Index: branches/wmf-deployment/extensions/CodeReview/CodeReleaseNotes.php |
— | — | @@ -1,168 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -class CodeReleaseNotes extends CodeView { |
5 | | - function __construct( $repoName ) { |
6 | | - global $wgRequest, $wgWikiSVN, $IP; |
7 | | - parent::__construct( $repoName ); |
8 | | - $this->mRepo = CodeRepository::newFromName( $repoName ); |
9 | | - $this->mPath = htmlspecialchars( trim( $wgRequest->getVal( 'path' ) ) ); |
10 | | - if ( strlen( $this->mPath ) && $this->mPath[0] !== '/' ) { |
11 | | - $this->mPath = "/{$this->mPath}"; // make sure this is a valid path |
12 | | - } |
13 | | - $this->mPath = preg_replace( '/\/$/', '', $this->mPath ); // kill last slash |
14 | | - $this->mStartRev = $wgRequest->getIntOrNull( 'startrev' ); |
15 | | - $this->mEndRev = $wgRequest->getIntOrNull( 'endrev' ); |
16 | | - # Default start rev to last live one if possible |
17 | | - if ( !$this->mStartRev && $this->mRepo && $this->mRepo->getName() == $wgWikiSVN ) { |
18 | | - $this->mStartRev = SpecialVersion::getSvnRevision( $IP ) + 1; |
19 | | - } |
20 | | - } |
21 | | - |
22 | | - function execute() { |
23 | | - if ( !$this->mRepo ) { |
24 | | - $view = new CodeRepoListView(); |
25 | | - $view->execute(); |
26 | | - return; |
27 | | - } |
28 | | - $this->showForm(); |
29 | | - # Sanity/performance check... |
30 | | - $lastRev = $this->mRepo->getLastStoredRev(); |
31 | | - if ( $this->mStartRev < ( $lastRev - 3000 ) ) |
32 | | - $this->mStartRev = NULL; |
33 | | - # Show notes if we have at least a starting revision |
34 | | - if ( $this->mStartRev ) { |
35 | | - $this->showReleaseNotes(); |
36 | | - } |
37 | | - } |
38 | | - |
39 | | - protected function showForm() { |
40 | | - global $wgOut, $wgScript, $wgUser; |
41 | | - $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/releasenotes' ); |
42 | | - $wgOut->addHTML( |
43 | | - Xml::openElement( 'form', array( 'action' => $wgScript, 'method' => 'get' ) ) . |
44 | | - "<fieldset><legend>" . wfMsgHtml( 'code-release-legend' ) . "</legend>" . |
45 | | - Xml::hidden( 'title', $special->getPrefixedDBKey() ) . '<b>' . |
46 | | - Xml::inputlabel( wfMsg( "code-release-startrev" ), 'startrev', 'startrev', 10, $this->mStartRev ) . |
47 | | - '</b> ' . |
48 | | - Xml::inputlabel( wfMsg( "code-release-endrev" ), 'endrev', 'endrev', 10, $this->mEndRev ) . |
49 | | - ' ' . |
50 | | - Xml::inputlabel( wfMsg( "code-pathsearch-path" ), 'path', 'path', 45, $this->mPath ) . |
51 | | - ' ' . |
52 | | - Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . |
53 | | - "</fieldset>" . Xml::closeElement( 'form' ) |
54 | | - ); |
55 | | - } |
56 | | - |
57 | | - protected function showReleaseNotes() { |
58 | | - global $wgOut; |
59 | | - $linker = new CodeCommentLinkerHtml( $this->mRepo ); |
60 | | - $dbr = wfGetDB( DB_SLAVE ); |
61 | | - if ( $this->mEndRev ) { |
62 | | - $where = 'cr_id BETWEEN ' . intval( $this->mStartRev ) . ' AND ' . intval( $this->mEndRev ); |
63 | | - } else { |
64 | | - $where = 'cr_id >= ' . intval( $this->mStartRev ); |
65 | | - } |
66 | | - if ( $this->mPath ) { |
67 | | - $where .= ' AND (cr_path LIKE ' . $dbr->addQuotes( $dbr->escapeLike( "{$this->mPath}/" ) . '%' ); |
68 | | - $where .= ' OR cr_path = ' . $dbr->addQuotes( $this->mPath ) . ')'; |
69 | | - } |
70 | | - # Select commits within this range... |
71 | | - $res = $dbr->select( array( 'code_rev', 'code_tags' ), |
72 | | - array( 'cr_message', 'cr_author', 'cr_id', 'ct_tag AS rnotes' ), |
73 | | - array( |
74 | | - 'cr_repo_id' => $this->mRepo->getId(), // this repo |
75 | | - "cr_status NOT IN('reverted','deferred','fixme')", // not reverted/deferred/fixme |
76 | | - "cr_message != ''", |
77 | | - $where // in range |
78 | | - ), |
79 | | - __METHOD__, |
80 | | - array( 'ORDER BY' => 'cr_id DESC' ), |
81 | | - array( 'code_tags' => array( 'LEFT JOIN', # Tagged for release notes? |
82 | | - 'ct_repo_id = cr_repo_id AND ct_rev_id = cr_id AND ct_tag = "release-notes"' ) |
83 | | - ) |
84 | | - ); |
85 | | - $wgOut->addHTML( '<ul>' ); |
86 | | - # Output any relevant seeming commits... |
87 | | - while ( $row = $dbr->fetchObject( $res ) ) { |
88 | | - $summary = htmlspecialchars( $row->cr_message ); |
89 | | - # Add this commit summary if needed. |
90 | | - if ( $row->rnotes || $this->isRelevant( $summary ) ) { |
91 | | - # Keep it short if possible... |
92 | | - $summary = $this->shortenSummary( $summary ); |
93 | | - # Anything left? (this can happen with some heuristics) |
94 | | - if ( $summary ) { |
95 | | - $summary = str_replace( "\n", "<br/>", $summary ); // Newlines -> <br/> |
96 | | - $wgOut->addHTML( "<li>" ); |
97 | | - $wgOut->addHTML( |
98 | | - $linker->link( $summary ) . " <i>(" . htmlspecialchars( $row->cr_author ) . |
99 | | - ', ' . $linker->link( "r{$row->cr_id}" ) . ")</i>" |
100 | | - ); |
101 | | - $wgOut->addHTML( "</li>\n" ); |
102 | | - } |
103 | | - } |
104 | | - } |
105 | | - $wgOut->addHTML( '</ul>' ); |
106 | | - } |
107 | | - |
108 | | - private function shortenSummary( $summary, $first = true ) { |
109 | | - # Astericks often used for point-by-point bullets |
110 | | - if ( preg_match( '/(^|\n) ?\*/', $summary ) ) { |
111 | | - $blurbs = explode( '*', $summary ); |
112 | | - # Double newlines separate importance generally |
113 | | - } else if ( strpos( $summary, "\n\n" ) !== false ) { |
114 | | - $blurbs = explode( "\n\n", $summary ); |
115 | | - } else { |
116 | | - return trim( $summary ); |
117 | | - } |
118 | | - $blurbs = array_map( 'trim', $blurbs ); # Clean up items |
119 | | - $blurbs = array_filter( $blurbs ); # Filter out any garbage |
120 | | - # Doesn't start with '*' and has some length? |
121 | | - # If so, then assume that the top bit is important. |
122 | | - if ( count( $blurbs ) ) { |
123 | | - $header = strpos( ltrim( $summary ), '*' ) !== 0 && str_word_count( $blurbs[0] ) >= 5; |
124 | | - } else { |
125 | | - $header = false; |
126 | | - } |
127 | | - # Keep it short if possible... |
128 | | - if ( count( $blurbs ) > 1 ) { |
129 | | - $summary = array(); |
130 | | - foreach ( $blurbs as $blurb ) { |
131 | | - # Always show the first bit |
132 | | - if ( $header && $first && count( $summary ) == 0 ) { |
133 | | - $summary[] = $this->shortenSummary( $blurb, true ); |
134 | | - # Is this bit important? Does it mention a revision? |
135 | | - } else if ( $this->isRelevant( $blurb ) || preg_match( '/\br(\d+)\b/', $blurb ) ) { |
136 | | - $bit = $this->shortenSummary( $blurb, false ); |
137 | | - if ( $bit ) $summary[] = $bit; |
138 | | - } |
139 | | - } |
140 | | - $summary = implode( "\n", $summary ); |
141 | | - } else { |
142 | | - $summary = implode( "\n", $blurbs ); |
143 | | - } |
144 | | - return $summary; |
145 | | - } |
146 | | - |
147 | | - // Quick relevance tests (these *should* be over-inclusive a little if anything) |
148 | | - private function isRelevant( $summary, $whole = true ) { |
149 | | - # Fixed a bug? Mentioned a config var? |
150 | | - if ( preg_match( '/\b(bug #?(\d+)|\$[we]g[0-9a-z]{3,50})\b/i', $summary ) ) |
151 | | - return true; |
152 | | - # Sanity check: summary cannot be *too* short to be useful |
153 | | - $words = str_word_count( $summary ); |
154 | | - if ( mb_strlen( $summary ) < 40 || $words <= 5 ) |
155 | | - return false; |
156 | | - # All caps words (like "BREAKING CHANGE"/magic words)? |
157 | | - if ( preg_match( '/\b[A-Z]{6,30}\b/', $summary ) ) |
158 | | - return true; |
159 | | - # Random keywords |
160 | | - if ( preg_match( '/\b(wiki|HTML\d|CSS\d|UTF-?8|(Apache|PHP|CGI|Java|Perl|Python|\w+SQL) ?\d?\.?\d?)\b/i', $summary ) ) |
161 | | - return true; |
162 | | - # Are we looking at the whole summary or an aspect of it? |
163 | | - if ( $whole ) { |
164 | | - return preg_match( '/(^|\n) ?\*/', $summary ); # List of items? |
165 | | - } else { |
166 | | - return true; |
167 | | - } |
168 | | - } |
169 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/CodeRevisionView.php |
— | — | @@ -1,570 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -// Special:Code/MediaWiki/40696 |
5 | | -class CodeRevisionView extends CodeView { |
6 | | - |
7 | | - function __construct( $repoName, $rev, $replyTarget = null ) { |
8 | | - global $wgRequest; |
9 | | - parent::__construct(); |
10 | | - $this->mRepo = CodeRepository::newFromName( $repoName ); |
11 | | - $this->mRev = $this->mRepo ? $this->mRepo->getRevision( intval( $rev ) ) : null; |
12 | | - $this->mPreviewText = false; |
13 | | - # URL params... |
14 | | - $this->mAddTags = $wgRequest->getText( 'wpTag' ); |
15 | | - $this->mRemoveTags = $wgRequest->getText( 'wpRemoveTag' ); |
16 | | - $this->mStatus = $wgRequest->getText( 'wpStatus' ); |
17 | | - $this->jumpToNext = $wgRequest->getCheck( 'wpSaveAndNext' ); |
18 | | - $this->mReplyTarget = $replyTarget ? |
19 | | - (int)$replyTarget : $wgRequest->getIntOrNull( 'wpParent' ); |
20 | | - $this->text = $wgRequest->getText( "wpReply{$this->mReplyTarget}" ); |
21 | | - $this->mSkipCache = ( $wgRequest->getVal( 'action' ) == 'purge' ); |
22 | | - # Make tag arrays |
23 | | - $this->mAddTags = $this->splitTags( $this->mAddTags ); |
24 | | - $this->mRemoveTags = $this->splitTags( $this->mRemoveTags ); |
25 | | - } |
26 | | - |
27 | | - function execute() { |
28 | | - global $wgOut, $wgUser, $wgLang; |
29 | | - if ( !$this->mRepo ) { |
30 | | - $view = new CodeRepoListView(); |
31 | | - $view->execute(); |
32 | | - return; |
33 | | - } |
34 | | - if ( !$this->mRev ) { |
35 | | - $view = new CodeRevisionListView( $this->mRepo->getName() ); |
36 | | - $view->execute(); |
37 | | - return; |
38 | | - } |
39 | | - $this->mStatus = $this->mStatus ? $this->mStatus : $this->mRev->getStatus(); |
40 | | - |
41 | | - $redirectOnPost = $this->checkPostings(); |
42 | | - if ( $redirectOnPost ) { |
43 | | - $wgOut->redirect( $redirectOnPost ); |
44 | | - return; |
45 | | - } |
46 | | - |
47 | | - $wgOut->setPageTitle( wfMsgHtml('code-rev-title',$this->mRev->getId()) ); |
48 | | - |
49 | | - $repoLink = $this->mSkin->link( SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() ), |
50 | | - htmlspecialchars( $this->mRepo->getName() ) ); |
51 | | - $revText = $this->navigationLinks(); |
52 | | - $paths = ''; |
53 | | - $modifiedPaths = $this->mRev->getModifiedPaths(); |
54 | | - foreach ( $modifiedPaths as $row ) { |
55 | | - $paths .= $this->formatPathLine( $row->cp_path, $row->cp_action ); |
56 | | - } |
57 | | - if ( $paths ) { |
58 | | - $paths = "<div class='mw-codereview-paths'><ul>\n$paths</ul></div>\n"; |
59 | | - } |
60 | | - $comments = $this->formatComments(); |
61 | | - $commentsLink = ""; |
62 | | - if ( $comments ) { |
63 | | - $commentsLink = " (<a href=\"#code-comments\">" . wfMsgHtml( 'code-comments' ) . "</a>)\n"; |
64 | | - } |
65 | | - $fields = array( |
66 | | - 'code-rev-repo' => $repoLink, |
67 | | - 'code-rev-rev' => $revText, |
68 | | - 'code-rev-date' => $wgLang->timeanddate( $this->mRev->getTimestamp(), true ), |
69 | | - 'code-rev-author' => $this->authorLink( $this->mRev->getAuthor() ), |
70 | | - 'code-rev-status' => $this->statusForm() . $commentsLink, |
71 | | - 'code-rev-tags' => $this->tagForm(), |
72 | | - 'code-rev-message' => $this->formatMessage( $this->mRev->getMessage() ), |
73 | | - 'code-rev-paths' => $paths, |
74 | | - ); |
75 | | - $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $this->mRev->getId() ); |
76 | | - |
77 | | - $html = Xml::openElement( 'form', array( 'action' => $special->getLocalUrl(), 'method' => 'post' ) ); |
78 | | - |
79 | | - if ( $wgUser->isAllowed( 'codereview-post-comment' ) ) { |
80 | | - $html .= $this->addActionButtons(); |
81 | | - } |
82 | | - |
83 | | - $html .= $this->formatMetaData( $fields ); |
84 | | - # Output diff |
85 | | - if ( $this->mRev->isDiffable() ) { |
86 | | - $diffHtml = $this->formatDiff(); |
87 | | - $html .= |
88 | | - "<h2>" . wfMsgHtml( 'code-rev-diff' ) . |
89 | | - ' <small>[' . $this->mSkin->makeLinkObj( $special, |
90 | | - wfMsg( 'code-rev-purge-link' ), 'action=purge' ) . ']</small></h2>' . |
91 | | - "<div class='mw-codereview-diff' id='mw-codereview-diff'>" . $diffHtml . "</div>\n"; |
92 | | - $html .= $this->formatImgDiff(); |
93 | | - } |
94 | | - # Show code relations |
95 | | - $relations = $this->formatReferences(); |
96 | | - if ( $relations ) { |
97 | | - $html .= "<h2 id='code-references'>" . wfMsgHtml( 'code-references' ) . |
98 | | - "</h2>\n" . $relations; |
99 | | - } |
100 | | - # Add revision comments |
101 | | - if ( $comments ) { |
102 | | - $html .= "<h2 id='code-comments'>" . wfMsgHtml( 'code-comments' ) . |
103 | | - "</h2>\n" . $comments; |
104 | | - } |
105 | | - |
106 | | - if ( $this->mReplyTarget ) { |
107 | | - global $wgJsMimeType; |
108 | | - $id = intval( $this->mReplyTarget ); |
109 | | - $html .= "<script type=\"$wgJsMimeType\">addOnloadHook(function(){" . |
110 | | - "document.getElementById('wpReplyTo$id').focus();" . |
111 | | - "});</script>\n"; |
112 | | - } |
113 | | - |
114 | | - if ( $wgUser->isAllowed( 'codereview-post-comment' ) ) { |
115 | | - $html .= $this->addActionButtons(); |
116 | | - } |
117 | | - |
118 | | - $changes = $this->formatPropChanges(); |
119 | | - if ( $changes ) { |
120 | | - $html .= "<h2 id='code-changes'>" . wfMsgHtml( 'code-prop-changes' ) . "</h2>\n" . $changes; |
121 | | - } |
122 | | - $html .= xml::closeElement( 'form' ); |
123 | | - |
124 | | - $wgOut->addHTML( $html ); |
125 | | - } |
126 | | - |
127 | | - protected function navigationLinks() { |
128 | | - global $wgLang; |
129 | | - |
130 | | - $rev = $this->mRev->getId(); |
131 | | - $prev = $this->mRev->getPrevious(); |
132 | | - $next = $this->mRev->getNext(); |
133 | | - $repo = $this->mRepo->getName(); |
134 | | - |
135 | | - $links = array(); |
136 | | - |
137 | | - if ( $prev ) { |
138 | | - $prevTarget = SpecialPage::getTitleFor( 'Code', "$repo/$prev" ); |
139 | | - $links[] = '< ' . $this->mSkin->link( $prevTarget, "r$prev" ); |
140 | | - } |
141 | | - |
142 | | - $revText = "<b>r$rev</b>"; |
143 | | - $viewvc = $this->mRepo->getViewVcBase(); |
144 | | - if ( $viewvc ) { |
145 | | - $url = htmlspecialchars( "$viewvc/?view=rev&revision=$rev" ); |
146 | | - $viewvcTxt = wfMsgHtml( 'code-rev-rev-viewvc' ); |
147 | | - $revText .= " (<a href=\"$url\" title=\"revision $rev\">$viewvcTxt</a>)"; |
148 | | - } |
149 | | - $links[] = $revText; |
150 | | - |
151 | | - if ( $next ) { |
152 | | - $nextTarget = SpecialPage::getTitleFor( 'Code', "$repo/$next" ); |
153 | | - $links[] = $this->mSkin->link( $nextTarget, "r$next" ) . ' >'; |
154 | | - } |
155 | | - |
156 | | - return $wgLang->pipeList( $links ); |
157 | | - } |
158 | | - |
159 | | - protected function checkPostings() { |
160 | | - global $wgRequest, $wgUser; |
161 | | - if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { |
162 | | - // Look for a posting... |
163 | | - $text = $wgRequest->getText( "wpReply{$this->mReplyTarget}" ); |
164 | | - $parent = $wgRequest->getIntOrNull( 'wpParent' ); |
165 | | - $review = $wgRequest->getInt( 'wpReview' ); |
166 | | - $isPreview = $wgRequest->getCheck( 'wpPreview' ); |
167 | | - if ( $isPreview ) { |
168 | | - // Save the text for reference on later comment display... |
169 | | - $this->mPreviewText = $text; |
170 | | - } |
171 | | - } |
172 | | - return false; |
173 | | - } |
174 | | - |
175 | | - protected function formatPathLine( $path, $action ) { |
176 | | - // Uses messages 'code-rev-modified-a', 'code-rev-modified-r', 'code-rev-modified-d', 'code-rev-modified-m' |
177 | | - $desc = wfMsgHtml( 'code-rev-modified-' . strtolower( $action ) ); |
178 | | - // Find any ' (from x)' from rename comment in the path. |
179 | | - preg_match( '/ \([^\)]+\)$/', $path, $matches ); |
180 | | - $from = isset( $matches[0] ) ? $matches[0] : ''; |
181 | | - // Remove ' (from x)' from rename comment in the path. |
182 | | - $path = preg_replace( '/ \([^\)]+\)$/', '', $path ); |
183 | | - $viewvc = $this->mRepo->getViewVcBase(); |
184 | | - $diff = ''; |
185 | | - if ( $viewvc ) { |
186 | | - $rev = $this->mRev->getId(); |
187 | | - $prev = $rev - 1; |
188 | | - $safePath = wfUrlEncode( $path ); |
189 | | - if ( $action !== 'D' ) { |
190 | | - $link = $this->mSkin->makeExternalLink( |
191 | | - "$viewvc$safePath?view=markup&pathrev=$rev", |
192 | | - $path . $from ); |
193 | | - } else { |
194 | | - $link = $safePath; |
195 | | - } |
196 | | - if ( $action !== 'A' && $action !== 'D' ) { |
197 | | - $diff = ' (' . |
198 | | - $this->mSkin->makeExternalLink( |
199 | | - "$viewvc$safePath?&pathrev=$rev&r1=$prev&r2=$rev", |
200 | | - wfMsg( 'code-rev-diff-link' ) ) . |
201 | | - ')'; |
202 | | - } |
203 | | - } else { |
204 | | - $link = $safePath; |
205 | | - } |
206 | | - return "<li>$link ($desc)$diff</li>\n"; |
207 | | - } |
208 | | - |
209 | | - protected function tagForm() { |
210 | | - global $wgUser; |
211 | | - $tags = $this->mRev->getTags(); |
212 | | - $list = ''; |
213 | | - if ( count( $tags ) ) { |
214 | | - $list = implode( ", ", |
215 | | - array_map( |
216 | | - array( $this, 'formatTag' ), |
217 | | - $tags ) |
218 | | - ) . ' '; |
219 | | - } |
220 | | - if ( $wgUser->isAllowed( 'codereview-add-tag' ) ) { |
221 | | - $list .= $this->addTagForm( $this->mAddTags, $this->mRemoveTags ); |
222 | | - } |
223 | | - return $list; |
224 | | - } |
225 | | - |
226 | | - protected function splitTags( $input ) { |
227 | | - if ( !$this->mRev ) return array(); |
228 | | - $tags = array_map( 'trim', explode( ",", $input ) ); |
229 | | - foreach ( $tags as $key => $tag ) { |
230 | | - $normal = $this->mRev->normalizeTag( $tag ); |
231 | | - if ( $normal === false ) { |
232 | | - return null; |
233 | | - } |
234 | | - $tags[$key] = $normal; |
235 | | - } |
236 | | - return $tags; |
237 | | - } |
238 | | - |
239 | | - static function listTags( $tags ) { |
240 | | - if ( empty( $tags ) ) |
241 | | - return ""; |
242 | | - return implode( ",", $tags ); |
243 | | - } |
244 | | - |
245 | | - protected function statusForm() { |
246 | | - global $wgUser; |
247 | | - if ( $wgUser->isAllowed( 'codereview-set-status' ) ) { |
248 | | - $repo = $this->mRepo->getName(); |
249 | | - $rev = $this->mRev->getId(); |
250 | | - return Xml::openElement( 'select', array( 'name' => 'wpStatus' ) ) . |
251 | | - self::buildStatusList( $this->mRev->getStatus(), $this ) . |
252 | | - xml::closeElement( 'select' ); |
253 | | - } else { |
254 | | - return htmlspecialchars( $this->statusDesc( $this->mRev->getStatus() ) ); |
255 | | - } |
256 | | - } |
257 | | - |
258 | | - static function buildStatusList( $status, $view ) { |
259 | | - $states = CodeRevision::getPossibleStates(); |
260 | | - $out = ''; |
261 | | - foreach ( $states as $state ) { |
262 | | - $out .= Xml::option( $view->statusDesc( $state ), $state, |
263 | | - $status === $state ); |
264 | | - } |
265 | | - return $out; |
266 | | - } |
267 | | - |
268 | | - /** Parameters are the tags to be added/removed sent with the request */ |
269 | | - static function addTagForm( $addTags, $removeTags ) { |
270 | | - global $wgUser; |
271 | | - return '<div><table><tr><td>' . |
272 | | - Xml::inputLabel( wfMsg( 'code-rev-tag-add' ), 'wpTag', 'wpTag', 20, |
273 | | - self::listTags( $addTags ) ) . '</td><td> </td><td>' . |
274 | | - Xml::inputLabel( wfMsg( 'code-rev-tag-remove' ), 'wpRemoveTag', 'wpRemoveTag', 20, |
275 | | - self::listTags( $removeTags ) ) . '</td></tr></table></div>'; |
276 | | - } |
277 | | - |
278 | | - protected function formatTag( $tag ) { |
279 | | - global $wgUser; |
280 | | - $repo = $this->mRepo->getName(); |
281 | | - $special = SpecialPage::getTitleFor( 'Code', "$repo/tag/$tag" ); |
282 | | - return $this->mSkin->link( $special, htmlspecialchars( $tag ) ); |
283 | | - } |
284 | | - |
285 | | - protected function formatDiff() { |
286 | | - global $wgEnableAPI; |
287 | | - |
288 | | - // Asynchronous diff loads will require the API |
289 | | - // And JS in the client, but tough shit eh? ;) |
290 | | - $deferDiffs = $wgEnableAPI; |
291 | | - |
292 | | - if ( $this->mSkipCache ) { |
293 | | - // We're purging the cache on purpose, probably |
294 | | - // because the cached data was corrupt. |
295 | | - $cache = 'skipcache'; |
296 | | - } elseif ( $deferDiffs ) { |
297 | | - // If data is already cached, we'll take it now; |
298 | | - // otherwise defer the load to an AJAX request. |
299 | | - // This lets the page be manipulable even if the |
300 | | - // SVN connection is slow or uncooperative. |
301 | | - $cache = 'cached'; |
302 | | - } else { |
303 | | - $cache = ''; |
304 | | - } |
305 | | - $diff = $this->mRepo->getDiff( $this->mRev->getId(), $cache ); |
306 | | - if ( !$diff && $deferDiffs ) { |
307 | | - // We'll try loading it by AJAX... |
308 | | - return $this->stubDiffLoader(); |
309 | | - } else { |
310 | | - $hilite = new CodeDiffHighlighter(); |
311 | | - return $hilite->render( $diff ); |
312 | | - } |
313 | | - } |
314 | | - |
315 | | - protected function formatImgDiff() { |
316 | | - global $wgCodeReviewImgRegex; |
317 | | - // Get image diffs |
318 | | - $imgDiffs = $html = ''; |
319 | | - $modifiedPaths = $this->mRev->getModifiedPaths(); |
320 | | - foreach ( $modifiedPaths as $row ) { |
321 | | - // Typical image file? |
322 | | - if( preg_match($wgCodeReviewImgRegex,$row->cp_path) ) { |
323 | | - $imgDiffs .= 'Index: '.htmlspecialchars( $row->cp_path )."\n"; |
324 | | - $imgDiffs .= '<table border="1px" style="background:white;"><tr>'; |
325 | | - if( $row->cp_action !== 'A' ) { // old |
326 | | - // What was done to it? |
327 | | - $action = $row->cp_action == 'D' ? 'code-rev-modified-d' : 'code-rev-modified-r'; |
328 | | - // Link to old image |
329 | | - $imgDiffs .= $this->formatImgCell( $row->cp_path, $this->mRev->getPrevious(), $action ); |
330 | | - } |
331 | | - if( $row->cp_action !== 'D' ) { // new |
332 | | - // What was done to it? |
333 | | - $action = $row->cp_action == 'A' ? 'code-rev-modified-a' : 'code-rev-modified-m'; |
334 | | - // Link to new image |
335 | | - $imgDiffs .= $this->formatImgCell( $row->cp_path, $this->mRev->getId(), $action ); |
336 | | - } |
337 | | - $imgDiffs .= "</tr></table>\n"; |
338 | | - } |
339 | | - } |
340 | | - if( $imgDiffs ) { |
341 | | - $html = '<h2>'.wfMsgHtml('code-rev-imagediff').'</h2>'; |
342 | | - $html .= "<div class='mw-codereview-imgdiff'>$imgDiffs</div>\n"; |
343 | | - } |
344 | | - return $html; |
345 | | - } |
346 | | - |
347 | | - protected function formatImgCell( $path, $rev, $message ) { |
348 | | - $viewvc = $this->mRepo->getViewVcBase(); |
349 | | - $safePath = wfUrlEncode( $path ); |
350 | | - $url = "{$viewvc}{$safePath}?&pathrev=$rev&revision=$rev"; |
351 | | - |
352 | | - $alt = wfMsg( $message ); |
353 | | - |
354 | | - return Xml::tags( 'td', |
355 | | - array(), |
356 | | - Xml::tags( 'a', |
357 | | - array( 'href' => $url ), |
358 | | - Xml::element( 'img', |
359 | | - array( |
360 | | - 'src' => $url, |
361 | | - 'alt' => $alt, |
362 | | - 'title' => $alt, |
363 | | - 'border' => '0' ) ) ) ); |
364 | | - } |
365 | | - |
366 | | - protected function stubDiffLoader() { |
367 | | - global $wgOut, $wgScriptPath, $wgCodeReviewStyleVersion; |
368 | | - $encRepo = Xml::encodeJsVar( $this->mRepo->getName() ); |
369 | | - $encRev = Xml::encodeJsVar( $this->mRev->getId() ); |
370 | | - $wgOut->addScriptFile( "$wgScriptPath/extensions/CodeReview/codereview.js?$wgCodeReviewStyleVersion" ); |
371 | | - $wgOut->addInlineScript( |
372 | | - "addOnloadHook( |
373 | | - function() { |
374 | | - CodeReview.loadDiff($encRepo,$encRev); |
375 | | - } |
376 | | - );" ); |
377 | | - return wfMsg( 'code-load-diff' ); |
378 | | - } |
379 | | - |
380 | | - protected function formatComments() { |
381 | | - $comments = implode( "\n", |
382 | | - array_map( array( $this, 'formatCommentInline' ), $this->mRev->getComments() ) |
383 | | - ); |
384 | | - if ( !$this->mReplyTarget ) { |
385 | | - $comments .= $this->postCommentForm(); |
386 | | - } |
387 | | - if ( !$comments ) { |
388 | | - return false; |
389 | | - } |
390 | | - return "<div class='mw-codereview-comments'>$comments</div>"; |
391 | | - } |
392 | | - |
393 | | - protected function formatPropChanges() { |
394 | | - $changes = implode( "\n", |
395 | | - array_map( array( $this, 'formatChangeInline' ), $this->mRev->getPropChanges() ) |
396 | | - ); |
397 | | - if ( !$changes ) { |
398 | | - return false; |
399 | | - } |
400 | | - return "<ul class='mw-codereview-changes'>$changes</ul>"; |
401 | | - } |
402 | | - |
403 | | - protected function formatReferences() { |
404 | | - $refs = implode( "\n", |
405 | | - array_map( array( $this, 'formatReferenceInline' ), $this->mRev->getReferences() ) |
406 | | - ); |
407 | | - if ( !$refs ) { |
408 | | - return false; |
409 | | - } |
410 | | - $header = '<th>'.wfMsg( 'code-field-id' ).'</th>'; |
411 | | - $header .= '<th>'.wfMsg( 'code-field-message' ) .'</th>'; |
412 | | - $header .= '<th>'.wfMsg( 'code-field-author' ).'</th>'; |
413 | | - $header .= '<th>'.wfMsg( 'code-field-timestamp' ).'</th>'; |
414 | | - return "<table border='1' class='TablePager'><tr>{$header}</tr>{$refs}</table>"; |
415 | | - } |
416 | | - |
417 | | - protected function formatCommentInline( $comment ) { |
418 | | - if ( $comment->id === $this->mReplyTarget ) { |
419 | | - return $this->formatComment( $comment, |
420 | | - $this->postCommentForm( $comment->id ) ); |
421 | | - } else { |
422 | | - return $this->formatComment( $comment ); |
423 | | - } |
424 | | - } |
425 | | - |
426 | | - protected function formatChangeInline( $change ) { |
427 | | - global $wgLang; |
428 | | - $revId = $change->rev->getId(); |
429 | | - $line = $wgLang->timeanddate( $change->timestamp, true ); |
430 | | - $line .= ' ' . $this->mSkin->userLink( $change->user, $change->userText ); |
431 | | - $line .= $this->mSkin->userToolLinks( $change->user, $change->userText ); |
432 | | - // Uses messages 'code-change-status', 'code-change-tags' |
433 | | - $line .= ' ' . wfMsgExt( "code-change-{$change->attrib}", 'parseinline', $revId ); |
434 | | - $line .= " <i>["; |
435 | | - if ( $change->removed ) { |
436 | | - $line .= '<b>' . wfMsg( 'code-change-removed' ) . '</b> '; |
437 | | - $line .= htmlspecialchars( $change->removed ); |
438 | | - $line .= $change->added ? " " : ""; |
439 | | - } |
440 | | - if ( $change->added ) { |
441 | | - $line .= '<b>' . wfMsg( 'code-change-added' ) . '</b> '; |
442 | | - $line .= htmlspecialchars( $change->added ); |
443 | | - } |
444 | | - $line .= "]</i>"; |
445 | | - return "<li>$line</li>"; |
446 | | - } |
447 | | - |
448 | | - protected function formatReferenceInline( $row ) { |
449 | | - global $wgLang; |
450 | | - $rev = intval( $row->cr_id ); |
451 | | - $repo = $this->mRepo->getName(); |
452 | | - // Borrow the code revision list css |
453 | | - $css = 'mw-codereview-status-' . htmlspecialchars( $row->cr_status ); |
454 | | - $date = $wgLang->timeanddate( $row->cr_timestamp, true ); |
455 | | - $title = SpecialPage::getTitleFor( 'Code', "$repo/$rev" ); |
456 | | - $revLink = $this->mSkin->link( $title, "r$rev" ); |
457 | | - $summary = $this->messageFragment( $row->cr_message ); |
458 | | - $author = $this->authorLink( $row->cr_author ); |
459 | | - return "<tr class='$css'><td>$revLink</td><td>$summary</td><td>$author</td><td>$date</td></tr>"; |
460 | | - } |
461 | | - |
462 | | - protected function commentLink( $commentId ) { |
463 | | - $repo = $this->mRepo->getName(); |
464 | | - $rev = $this->mRev->getId(); |
465 | | - $title = SpecialPage::getTitleFor( 'Code', "$repo/$rev" ); |
466 | | - $title->setFragment( "#c{$commentId}" ); |
467 | | - return $title; |
468 | | - } |
469 | | - |
470 | | - protected function revLink() { |
471 | | - $repo = $this->mRepo->getName(); |
472 | | - $rev = $this->mRev->getId(); |
473 | | - $title = SpecialPage::getTitleFor( 'Code', "$repo/$rev" ); |
474 | | - return $title; |
475 | | - } |
476 | | - |
477 | | - protected function previewComment( $text, $review = 0 ) { |
478 | | - $comment = $this->mRev->previewComment( $text, $review ); |
479 | | - return $this->formatComment( $comment ); |
480 | | - } |
481 | | - |
482 | | - protected function formatComment( $comment, $replyForm = '' ) { |
483 | | - global $wgOut, $wgLang; |
484 | | - $linker = new CodeCommentLinkerWiki( $this->mRepo ); |
485 | | - |
486 | | - if ( $comment->id === null ) { |
487 | | - $linkId = 'cpreview'; |
488 | | - $permaLink = "<b>Preview:</b> "; |
489 | | - } else { |
490 | | - $linkId = 'c' . intval( $comment->id ); |
491 | | - $permaLink = $this->mSkin->link( $this->commentLink( $comment->id ), "#" ); |
492 | | - } |
493 | | - |
494 | | - return Xml::openElement( 'div', |
495 | | - array( |
496 | | - 'class' => 'mw-codereview-comment', |
497 | | - 'id' => $linkId, |
498 | | - 'style' => $this->commentStyle( $comment ) ) ) . |
499 | | - '<div class="mw-codereview-comment-meta">' . |
500 | | - $permaLink . |
501 | | - wfMsgHtml( 'code-rev-comment-by', |
502 | | - $this->mSkin->userLink( $comment->user, $comment->userText ) . |
503 | | - $this->mSkin->userToolLinks( $comment->user, $comment->userText ) ) . |
504 | | - ' ' . |
505 | | - $wgLang->timeanddate( $comment->timestamp, true ) . |
506 | | - ' ' . |
507 | | - $this->commentReplyLink( $comment->id ) . |
508 | | - '</div>' . |
509 | | - '<div class="mw-codereview-comment-text">' . |
510 | | - $wgOut->parse( $linker->link( $comment->text ) ) . |
511 | | - '</div>' . |
512 | | - $replyForm . |
513 | | - '</div>'; |
514 | | - } |
515 | | - |
516 | | - protected function commentStyle( $comment ) { |
517 | | - $depth = $comment->threadDepth(); |
518 | | - $margin = ( $depth - 1 ) * 48; |
519 | | - return "margin-left: ${margin}px"; |
520 | | - } |
521 | | - |
522 | | - protected function commentReplyLink( $id ) { |
523 | | - global $wgUser; |
524 | | - if ( !$wgUser->isAllowed( 'codereview-post-comment' ) ) return ''; |
525 | | - $repo = $this->mRepo->getName(); |
526 | | - $rev = $this->mRev->getId(); |
527 | | - $self = SpecialPage::getTitleFor( 'Code', "$repo/$rev/reply/$id" ); |
528 | | - $self->setFragment( "#c$id" ); |
529 | | - return '[' . $this->mSkin->link( $self, wfMsg( 'codereview-reply-link' ) ) . ']'; |
530 | | - } |
531 | | - |
532 | | - protected function postCommentForm( $parent = null ) { |
533 | | - global $wgUser; |
534 | | - if ( $this->mPreviewText !== false && $parent === $this->mReplyTarget ) { |
535 | | - $preview = $this->previewComment( $this->mPreviewText ); |
536 | | - $text = htmlspecialchars( $this->mPreviewText ); |
537 | | - } else { |
538 | | - $preview = ''; |
539 | | - $text = $this->text; |
540 | | - } |
541 | | - $repo = $this->mRepo->getName(); |
542 | | - $rev = $this->mRev->getId(); |
543 | | - if ( !$wgUser->isAllowed( 'codereview-post-comment' ) ) { |
544 | | - return ''; |
545 | | - } |
546 | | - return '<div class="mw-codereview-post-comment">' . |
547 | | - $preview . |
548 | | - Xml::hidden( 'wpEditToken', $wgUser->editToken() ) . |
549 | | - ( $parent ? Xml::hidden( 'wpParent', $parent ) : '' ) . |
550 | | - '<div>' . |
551 | | - Xml::openElement( 'textarea', array( |
552 | | - 'name' => "wpReply{$parent}", |
553 | | - 'id' => "wpReplyTo{$parent}", |
554 | | - 'cols' => 40, |
555 | | - 'rows' => 5 ) ) . |
556 | | - $text . |
557 | | - '</textarea>' . |
558 | | - '</div>' . |
559 | | - '</div>'; |
560 | | - } |
561 | | - |
562 | | - protected function addActionButtons() { |
563 | | - return '<div>' . |
564 | | - Xml::submitButton( wfMsg( 'code-rev-submit' ), array( 'name' => 'wpSave' ) ) . |
565 | | - ' ' . |
566 | | - Xml::submitButton( wfMsg( 'code-rev-submit-next' ), array( 'name' => 'wpSaveAndNext' ) ) . |
567 | | - ' ' . |
568 | | - Xml::submitButton( wfMsg( 'code-rev-comment-preview' ), array( 'name' => 'wpPreview' ) ) . |
569 | | - '</div>'; |
570 | | - } |
571 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/CodeRepoListView.php |
— | — | @@ -1,33 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -// Special:Code |
5 | | -class CodeRepoListView { |
6 | | - |
7 | | - function execute() { |
8 | | - global $wgOut; |
9 | | - $repos = CodeRepository::getRepoList(); |
10 | | - if ( !count( $repos ) ) { |
11 | | - $wgOut->addWikiMsg( 'code-no-repo' ); |
12 | | - return; |
13 | | - } |
14 | | - $text = ''; |
15 | | - foreach ( $repos as $repo ) { |
16 | | - $name = $repo->getName(); |
17 | | - $text .= "* " . self::getNavItem( $name ) . "\n"; |
18 | | - } |
19 | | - $wgOut->addWikiText( $text ); |
20 | | - } |
21 | | - |
22 | | - public static function getNavItem( $name ) { |
23 | | - global $wgLang; |
24 | | - $text = "'''[[Special:Code/$name|$name]]''' ("; |
25 | | - $links[] = "[[Special:Code/$name/comments|" . wfMsgHtml( 'code-notes' ) . "]]"; |
26 | | - $links[] = "[[Special:Code/$name/status|" . wfMsgHtml( 'code-status' ) . "]]"; |
27 | | - $links[] = "[[Special:Code/$name/tag|" . wfMsgHtml( 'code-tags' ) . "]]"; |
28 | | - $links[] = "[[Special:Code/$name/author|" . wfMsgHtml( 'code-authors' ) . "]]"; |
29 | | - $links[] = "[[Special:Code/$name/releasenotes|" . wfMsgHtml( 'code-releasenotes' ) . "]]"; |
30 | | - $text .= $wgLang->pipeList( $links ); |
31 | | - $text .= ")"; |
32 | | - return $text; |
33 | | - } |
34 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/CodeRevision.php |
— | — | @@ -1,618 +0,0 @@ |
2 | | -<?php |
3 | | -if ( !defined( 'MEDIAWIKI' ) ) die(); |
4 | | - |
5 | | -class CodeRevision { |
6 | | - public static function newFromSvn( CodeRepository $repo, $data ) { |
7 | | - $rev = new CodeRevision(); |
8 | | - $rev->mRepoId = $repo->getId(); |
9 | | - $rev->mRepo = $repo; |
10 | | - $rev->mId = intval( $data['rev'] ); |
11 | | - $rev->mAuthor = $data['author']; |
12 | | - $rev->mTimestamp = wfTimestamp( TS_MW, strtotime( $data['date'] ) ); |
13 | | - $rev->mMessage = rtrim( $data['msg'] ); |
14 | | - $rev->mPaths = $data['paths']; |
15 | | - $rev->mStatus = 'new'; |
16 | | - |
17 | | - $common = null; |
18 | | - if ( $rev->mPaths ) { |
19 | | - if ( count( $rev->mPaths ) == 1 ) |
20 | | - $common = $rev->mPaths[0]['path']; |
21 | | - else { |
22 | | - $first = array_shift( $rev->mPaths ); |
23 | | - |
24 | | - $common = explode( '/', $first['path'] ); |
25 | | - |
26 | | - foreach ( $rev->mPaths as $path ) { |
27 | | - $compare = explode( '/', $path['path'] ); |
28 | | - |
29 | | - // make sure $common is the shortest path |
30 | | - if ( count( $compare ) < count( $common ) ) |
31 | | - list( $compare, $common ) = array( $common, $compare ); |
32 | | - |
33 | | - $tmp = array(); |
34 | | - foreach ( $common as $k => $v ) |
35 | | - if ( $v == $compare[$k] ) $tmp[] = $v; |
36 | | - else break; |
37 | | - $common = $tmp; |
38 | | - } |
39 | | - $common = implode( '/', $common ); |
40 | | - |
41 | | - array_unshift( $rev->mPaths, $first ); |
42 | | - } |
43 | | - } |
44 | | - $rev->mCommonPath = $common; |
45 | | - return $rev; |
46 | | - } |
47 | | - |
48 | | - public static function newFromRow( CodeRepository $repo, $row ) { |
49 | | - $rev = new CodeRevision(); |
50 | | - $rev->mRepoId = intval( $row->cr_repo_id ); |
51 | | - if ( $rev->mRepoId != $repo->getId() ) { |
52 | | - throw new MWException( "Invalid repo ID in " . __METHOD__ ); |
53 | | - } |
54 | | - $rev->mRepo = $repo; |
55 | | - $rev->mId = intval( $row->cr_id ); |
56 | | - $rev->mAuthor = $row->cr_author; |
57 | | - $rev->mTimestamp = wfTimestamp( TS_MW, $row->cr_timestamp ); |
58 | | - $rev->mMessage = $row->cr_message; |
59 | | - $rev->mStatus = $row->cr_status; |
60 | | - $rev->mCommonPath = $row->cr_path; |
61 | | - return $rev; |
62 | | - } |
63 | | - |
64 | | - public function getId() { |
65 | | - return intval( $this->mId ); |
66 | | - } |
67 | | - |
68 | | - public function getRepoId() { |
69 | | - return intval( $this->mRepoId ); |
70 | | - } |
71 | | - |
72 | | - public function getAuthor() { |
73 | | - return $this->mAuthor; |
74 | | - } |
75 | | - |
76 | | - public function getWikiUser() { |
77 | | - return $this->mRepo->authorWikiUser( $this->getAuthor() ); |
78 | | - } |
79 | | - |
80 | | - public function getTimestamp() { |
81 | | - return $this->mTimestamp; |
82 | | - } |
83 | | - |
84 | | - public function getMessage() { |
85 | | - return $this->mMessage; |
86 | | - } |
87 | | - |
88 | | - public function getStatus() { |
89 | | - return $this->mStatus; |
90 | | - } |
91 | | - |
92 | | - public function getCommonPath() { |
93 | | - return $this->mCommonPath; |
94 | | - } |
95 | | - |
96 | | - public static function getPossibleStates() { |
97 | | - return array( 'new', 'fixme', 'reverted', 'resolved', 'ok', 'verified', 'deferred' ); |
98 | | - } |
99 | | - |
100 | | - public function isValidStatus( $status ) { |
101 | | - return in_array( $status, self::getPossibleStates(), true ); |
102 | | - } |
103 | | - |
104 | | - public function setStatus( $status, $user ) { |
105 | | - if ( !$this->isValidStatus( $status ) ) { |
106 | | - throw new MWException( "Tried to save invalid code revision status" ); |
107 | | - } |
108 | | - // Get the old status from the master |
109 | | - $dbw = wfGetDB( DB_MASTER ); |
110 | | - $oldStatus = $dbw->selectField( 'code_rev', |
111 | | - 'cr_status', |
112 | | - array( 'cr_repo_id' => $this->mRepoId, 'cr_id' => $this->mId ), |
113 | | - __METHOD__ |
114 | | - ); |
115 | | - if ( $oldStatus === $status ) { |
116 | | - return false; // nothing to do here |
117 | | - } |
118 | | - // Update status |
119 | | - $this->mStatus = $status; |
120 | | - $dbw->update( 'code_rev', |
121 | | - array( 'cr_status' => $status ), |
122 | | - array( |
123 | | - 'cr_repo_id' => $this->mRepoId, |
124 | | - 'cr_id' => $this->mId ), |
125 | | - __METHOD__ |
126 | | - ); |
127 | | - // Log this change |
128 | | - if ( $user && $user->getId() ) { |
129 | | - $dbw->insert( 'code_prop_changes', |
130 | | - array( |
131 | | - 'cpc_repo_id' => $this->getRepoId(), |
132 | | - 'cpc_rev_id' => $this->getId(), |
133 | | - 'cpc_attrib' => 'status', |
134 | | - 'cpc_removed' => $oldStatus, |
135 | | - 'cpc_added' => $status, |
136 | | - 'cpc_timestamp' => $dbw->timestamp(), |
137 | | - 'cpc_user' => $user->getId(), |
138 | | - 'cpc_user_text' => $user->getName() |
139 | | - ), |
140 | | - __METHOD__ |
141 | | - ); |
142 | | - } |
143 | | - return true; |
144 | | - } |
145 | | - |
146 | | - public function save() { |
147 | | - $dbw = wfGetDB( DB_MASTER ); |
148 | | - $dbw->begin(); |
149 | | - |
150 | | - $dbw->insert( 'code_rev', |
151 | | - array( |
152 | | - 'cr_repo_id' => $this->mRepoId, |
153 | | - 'cr_id' => $this->mId, |
154 | | - 'cr_author' => $this->mAuthor, |
155 | | - 'cr_timestamp' => $dbw->timestamp( $this->mTimestamp ), |
156 | | - 'cr_message' => $this->mMessage, |
157 | | - 'cr_status' => $this->mStatus, |
158 | | - 'cr_path' => $this->mCommonPath ), |
159 | | - __METHOD__, |
160 | | - array( 'IGNORE' ) |
161 | | - ); |
162 | | - // Already exists? Update the row! |
163 | | - $newRevision = $dbw->affectedRows() > 0; |
164 | | - if ( !$newRevision ) { |
165 | | - $dbw->update( 'code_rev', |
166 | | - array( |
167 | | - 'cr_author' => $this->mAuthor, |
168 | | - 'cr_timestamp' => $dbw->timestamp( $this->mTimestamp ), |
169 | | - 'cr_message' => $this->mMessage, |
170 | | - 'cr_path' => $this->mCommonPath ), |
171 | | - array( |
172 | | - 'cr_repo_id' => $this->mRepoId, |
173 | | - 'cr_id' => $this->mId ), |
174 | | - __METHOD__ |
175 | | - ); |
176 | | - } |
177 | | - // Update path tracking used for output and searching |
178 | | - if ( $this->mPaths ) { |
179 | | - $data = array(); |
180 | | - foreach ( $this->mPaths as $path ) { |
181 | | - $data[] = array( |
182 | | - 'cp_repo_id' => $this->mRepoId, |
183 | | - 'cp_rev_id' => $this->mId, |
184 | | - 'cp_path' => $path['path'], |
185 | | - 'cp_action' => $path['action'] ); |
186 | | - } |
187 | | - $dbw->insert( 'code_paths', $data, __METHOD__, array( 'IGNORE' ) ); |
188 | | - } |
189 | | - // Update bug references table... |
190 | | - $affectedBugs = array(); |
191 | | - if ( preg_match_all( '/\bbug (\d+)\b/', $this->mMessage, $m ) ) { |
192 | | - $data = array(); |
193 | | - foreach( $m[1] as $bug ) { |
194 | | - $data[] = array( |
195 | | - 'cb_repo_id' => $this->mRepoId, |
196 | | - 'cb_from' => $this->mId, |
197 | | - 'cb_bug' => $bug |
198 | | - ); |
199 | | - $affectedBugs[] = intval($bug); |
200 | | - } |
201 | | - $dbw->insert( 'code_bugs', $data, __METHOD__, array( 'IGNORE' ) ); |
202 | | - } |
203 | | - // Get the revisions this commit references... |
204 | | - $affectedRevs = array(); |
205 | | - if ( preg_match_all( '/\br(\d{2,})\b/', $this->mMessage, $m ) ) { |
206 | | - foreach( $m[1] as $rev ) { |
207 | | - $affectedRevs[] = intval($rev); |
208 | | - } |
209 | | - } |
210 | | - // Also, get previous revisions that have bugs in common... |
211 | | - if( count($affectedBugs) ) { |
212 | | - $res = $dbw->select( 'code_bugs', |
213 | | - array( 'cb_from' ), |
214 | | - array( |
215 | | - 'cb_repo_id' => $this->mRepoId, |
216 | | - 'cb_bug' => $affectedBugs, |
217 | | - 'cb_from < '.intval($this->mId), # just in case |
218 | | - ), |
219 | | - __METHOD__, |
220 | | - array( 'USE INDEX' => 'cb_repo_id' ) |
221 | | - ); |
222 | | - foreach( $res as $row ) { |
223 | | - $affectedRevs[] = intval($row->cb_from); |
224 | | - } |
225 | | - } |
226 | | - // Filter any duplicate revisions |
227 | | - if( count($affectedRevs) ) { |
228 | | - $data = array(); |
229 | | - $affectedRevs = array_unique($affectedRevs); |
230 | | - foreach( $affectedRevs as $rev ) { |
231 | | - $data[] = array( |
232 | | - 'cf_repo_id' => $this->mRepoId, |
233 | | - 'cf_from' => $this->mId, |
234 | | - 'cf_to' => $rev |
235 | | - ); |
236 | | - $affectedRevs[] = intval($rev); |
237 | | - } |
238 | | - $dbw->insert( 'code_relations', $data, __METHOD__, array( 'IGNORE' ) ); |
239 | | - } |
240 | | - // Email the authors of revisions that this follows up on |
241 | | - if( $newRevision && count($affectedRevs) > 0 ) { |
242 | | - // Get committer wiki user name, or repo name at least |
243 | | - $user = $this->mRepo->authorWikiUser( $this->mAuthor ); |
244 | | - $committer = $user ? $user->getName() : htmlspecialchars($this->mAuthor); |
245 | | - // Get the authors of these revisions |
246 | | - $res = $dbw->select( 'code_rev', |
247 | | - array( 'cr_author', 'cr_id' ), |
248 | | - array( |
249 | | - 'cr_repo_id' => $this->mRepoId, |
250 | | - 'cr_id' => $affectedRevs, |
251 | | - 'cr_id < '.intval($this->mId), # just in case |
252 | | - // No sense in notifying if it's the same person |
253 | | - 'cr_author != '.$dbw->addQuotes($this->mAuthor) |
254 | | - ), |
255 | | - __METHOD__, |
256 | | - array( 'USE INDEX' => 'PRIMARY' ) |
257 | | - ); |
258 | | - // Get repo and build comment title (for url) |
259 | | - $title = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $this->mId ); |
260 | | - $url = $title->getFullUrl(); |
261 | | - wfLoadExtensionMessages( 'CodeReview' ); |
262 | | - foreach( $res as $row ) { |
263 | | - $user = $this->mRepo->authorWikiUser( $row->cr_author ); |
264 | | - // User must exist on wiki and have a valid email addy |
265 | | - if( !$user || !$user->canReceiveEmail() ) continue; |
266 | | - // Send message in receiver's language |
267 | | - $lang = array( 'language' => $user->getOption( 'language' ) ); |
268 | | - $user->sendMail( |
269 | | - wfMsgExt( 'codereview-email-subj2', $lang, $this->mRepo->getName(), $row->cr_id ), |
270 | | - wfMsgExt( 'codereview-email-body2', $lang, $committer, $row->cr_id, $url, $this->mMessage ) |
271 | | - ); |
272 | | - } |
273 | | - } |
274 | | - $dbw->commit(); |
275 | | - } |
276 | | - |
277 | | - public function getModifiedPaths() { |
278 | | - $dbr = wfGetDB( DB_SLAVE ); |
279 | | - return $dbr->select( |
280 | | - 'code_paths', |
281 | | - array( 'cp_path', 'cp_action' ), |
282 | | - array( 'cp_repo_id' => $this->mRepoId, 'cp_rev_id' => $this->mId ), |
283 | | - __METHOD__ |
284 | | - ); |
285 | | - } |
286 | | - |
287 | | - public function isDiffable() { |
288 | | - $paths = $this->getModifiedPaths(); |
289 | | - if ( !$paths->numRows() || $paths->numRows() > 20 ) { |
290 | | - return false; // things need to get done this year |
291 | | - } |
292 | | - return true; |
293 | | - } |
294 | | - |
295 | | - public function previewComment( $text, $review, $parent = null ) { |
296 | | - $data = $this->commentData( $text, $review, $parent ); |
297 | | - $data['cc_id'] = null; |
298 | | - return CodeComment::newFromData( $this, $data ); |
299 | | - } |
300 | | - |
301 | | - public function saveComment( $text, $review, $parent = null ) { |
302 | | - global $wgUser; |
303 | | - if ( !strlen( $text ) ) { |
304 | | - return 0; |
305 | | - } |
306 | | - $dbw = wfGetDB( DB_MASTER ); |
307 | | - $data = $this->commentData( $text, $review, $parent ); |
308 | | - |
309 | | - $dbw->begin(); |
310 | | - $data['cc_id'] = $dbw->nextSequenceValue( 'code_comment_cc_id' ); |
311 | | - $dbw->insert( 'code_comment', $data, __METHOD__ ); |
312 | | - $commentId = $dbw->insertId(); |
313 | | - $dbw->commit(); |
314 | | - |
315 | | - // Give email notices to committer and commenters |
316 | | - global $wgCodeReviewENotif, $wgEnableEmail; |
317 | | - if ( $wgCodeReviewENotif && $wgEnableEmail ) { |
318 | | - // Make list of users to send emails to |
319 | | - $users = $this->getCommentingUsers(); |
320 | | - if ( $user = $this->getWikiUser() ) { |
321 | | - $users[$user->getId()] = $user; |
322 | | - } |
323 | | - // Get repo and build comment title (for url) |
324 | | - $title = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $this->mId ); |
325 | | - $title->setFragment( "#c{$commentId}" ); |
326 | | - $url = $title->getFullUrl(); |
327 | | - foreach ( $users as $userId => $user ) { |
328 | | - // No sense in notifying this commenter |
329 | | - if ( $wgUser->getId() == $user->getId() ) { |
330 | | - continue; |
331 | | - } |
332 | | - // Send message in receiver's language |
333 | | - $lang = array( 'language' => $user->getOption( 'language' ) ); |
334 | | - if ( $user->canReceiveEmail() ) { |
335 | | - $user->sendMail( |
336 | | - wfMsgExt( 'codereview-email-subj', $lang, $this->mRepo->getName(), $this->mId ), |
337 | | - wfMsgExt( 'codereview-email-body', $lang, $wgUser->getName(), $url, $this->mId, $text ) |
338 | | - ); |
339 | | - } |
340 | | - } |
341 | | - } |
342 | | - |
343 | | - return $commentId; |
344 | | - } |
345 | | - |
346 | | - protected function commentData( $text, $review, $parent = null ) { |
347 | | - global $wgUser; |
348 | | - $dbw = wfGetDB( DB_MASTER ); |
349 | | - $ts = wfTimestamp( TS_MW ); |
350 | | - $sortkey = $this->threadedSortkey( $parent, $ts ); |
351 | | - return array( |
352 | | - 'cc_repo_id' => $this->mRepoId, |
353 | | - 'cc_rev_id' => $this->mId, |
354 | | - 'cc_text' => $text, |
355 | | - 'cc_parent' => $parent, |
356 | | - 'cc_user' => $wgUser->getId(), |
357 | | - 'cc_user_text' => $wgUser->getName(), |
358 | | - 'cc_timestamp' => $dbw->timestamp( $ts ), |
359 | | - 'cc_review' => $review, |
360 | | - 'cc_sortkey' => $sortkey ); |
361 | | - } |
362 | | - |
363 | | - protected function threadedSortKey( $parent, $ts ) { |
364 | | - if ( $parent ) { |
365 | | - // We construct a threaded sort key by concatenating the timestamps |
366 | | - // of all our parent comments |
367 | | - $dbw = wfGetDB( DB_MASTER ); |
368 | | - $parentKey = $dbw->selectField( 'code_comment', |
369 | | - 'cc_sortkey', |
370 | | - array( 'cc_id' => $parent ), |
371 | | - __METHOD__ ); |
372 | | - if ( $parentKey ) { |
373 | | - return $parentKey . ',' . $ts; |
374 | | - } else { |
375 | | - // hmmmm |
376 | | - throw new MWException( 'Invalid parent submission' ); |
377 | | - } |
378 | | - } else { |
379 | | - return $ts; |
380 | | - } |
381 | | - } |
382 | | - |
383 | | - public function getComments() { |
384 | | - $dbr = wfGetDB( DB_SLAVE ); |
385 | | - $result = $dbr->select( 'code_comment', |
386 | | - array( |
387 | | - 'cc_id', |
388 | | - 'cc_text', |
389 | | - 'cc_parent', |
390 | | - 'cc_user', |
391 | | - 'cc_user_text', |
392 | | - 'cc_timestamp', |
393 | | - 'cc_review', |
394 | | - 'cc_sortkey' ), |
395 | | - array( |
396 | | - 'cc_repo_id' => $this->mRepoId, |
397 | | - 'cc_rev_id' => $this->mId ), |
398 | | - __METHOD__, |
399 | | - array( |
400 | | - 'ORDER BY' => 'cc_sortkey' ) |
401 | | - ); |
402 | | - $comments = array(); |
403 | | - foreach ( $result as $row ) { |
404 | | - $comments[] = CodeComment::newFromRow( $this, $row ); |
405 | | - } |
406 | | - $result->free(); |
407 | | - return $comments; |
408 | | - } |
409 | | - |
410 | | - public function getPropChanges() { |
411 | | - $dbr = wfGetDB( DB_SLAVE ); |
412 | | - $result = $dbr->select( array( 'code_prop_changes', 'user' ), |
413 | | - array( |
414 | | - 'cpc_attrib', |
415 | | - 'cpc_removed', |
416 | | - 'cpc_added', |
417 | | - 'cpc_timestamp', |
418 | | - 'cpc_user', |
419 | | - 'cpc_user_text', |
420 | | - 'user_name' |
421 | | - ), array( |
422 | | - 'cpc_repo_id' => $this->mRepoId, |
423 | | - 'cpc_rev_id' => $this->mId, |
424 | | - ), |
425 | | - __METHOD__, |
426 | | - array( 'ORDER BY' => 'cpc_timestamp DESC' ), |
427 | | - array( 'user' => array( 'LEFT JOIN', 'cpc_user = user_id' ) ) |
428 | | - ); |
429 | | - $changes = array(); |
430 | | - foreach ( $result as $row ) { |
431 | | - $changes[] = CodePropChange::newFromRow( $this, $row ); |
432 | | - } |
433 | | - $result->free(); |
434 | | - return $changes; |
435 | | - } |
436 | | - |
437 | | - protected function getCommentingUsers() { |
438 | | - $dbr = wfGetDB( DB_SLAVE ); |
439 | | - $res = $dbr->select( 'code_comment', |
440 | | - 'DISTINCT(cc_user)', |
441 | | - array( |
442 | | - 'cc_repo_id' => $this->mRepoId, |
443 | | - 'cc_rev_id' => $this->mId, |
444 | | - 'cc_user != 0' // users only |
445 | | - ), |
446 | | - __METHOD__ |
447 | | - ); |
448 | | - $users = array(); |
449 | | - while ( $row = $res->fetchObject() ) { |
450 | | - $users[$row->cc_user] = User::newFromId( $row->cc_user ); |
451 | | - } |
452 | | - return $users; |
453 | | - } |
454 | | - |
455 | | - public function getReferences() { |
456 | | - $refs = array(); |
457 | | - $dbr = wfGetDB( DB_SLAVE ); |
458 | | - $res = $dbr->select( |
459 | | - array( 'code_relations', 'code_rev' ), |
460 | | - array( 'cr_id', 'cr_status', 'cr_timestamp', 'cr_author', 'cr_message' ), |
461 | | - array( |
462 | | - 'cf_repo_id' => $this->mRepoId, |
463 | | - 'cf_to' => $this->mId, |
464 | | - 'cr_repo_id = cf_repo_id', |
465 | | - 'cr_id = cf_from' |
466 | | - ), |
467 | | - __METHOD__ |
468 | | - ); |
469 | | - while ( $row = $res->fetchObject() ) { |
470 | | - $refs[] = $row; |
471 | | - } |
472 | | - return $refs; |
473 | | - } |
474 | | - |
475 | | - public function getTags( $from = DB_SLAVE ) { |
476 | | - $db = wfGetDB( $from ); |
477 | | - $result = $db->select( 'code_tags', |
478 | | - array( 'ct_tag' ), |
479 | | - array( |
480 | | - 'ct_repo_id' => $this->mRepoId, |
481 | | - 'ct_rev_id' => $this->mId ), |
482 | | - __METHOD__ ); |
483 | | - |
484 | | - $tags = array(); |
485 | | - foreach ( $result as $row ) { |
486 | | - $tags[] = $row->ct_tag; |
487 | | - } |
488 | | - return $tags; |
489 | | - } |
490 | | - |
491 | | - public function changeTags( $addTags, $removeTags, $user = NULL ) { |
492 | | - // Get the current tags and see what changes |
493 | | - $tagsNow = $this->getTags( DB_MASTER ); |
494 | | - // Normalize our input tags |
495 | | - $addTags = $this->normalizeTags( $addTags ); |
496 | | - $removeTags = $this->normalizeTags( $removeTags ); |
497 | | - $addTags = array_diff( $addTags, $tagsNow ); |
498 | | - $removeTags = array_intersect( $removeTags, $tagsNow ); |
499 | | - // Do the queries |
500 | | - $dbw = wfGetDB( DB_MASTER ); |
501 | | - if ( $addTags ) { |
502 | | - $dbw->insert( 'code_tags', |
503 | | - $this->tagData( $addTags ), |
504 | | - __METHOD__, |
505 | | - array( 'IGNORE' ) |
506 | | - ); |
507 | | - } |
508 | | - if ( $removeTags ) { |
509 | | - $dbw->delete( 'code_tags', |
510 | | - array( |
511 | | - 'ct_repo_id' => $this->mRepoId, |
512 | | - 'ct_rev_id' => $this->mId, |
513 | | - 'ct_tag' => $removeTags ), |
514 | | - __METHOD__ |
515 | | - ); |
516 | | - } |
517 | | - // Log this change |
518 | | - if ( ( $removeTags || $addTags ) && $user && $user->getId() ) { |
519 | | - $dbw->insert( 'code_prop_changes', |
520 | | - array( |
521 | | - 'cpc_repo_id' => $this->getRepoId(), |
522 | | - 'cpc_rev_id' => $this->getId(), |
523 | | - 'cpc_attrib' => 'tags', |
524 | | - 'cpc_removed' => implode( ',', $removeTags ), |
525 | | - 'cpc_added' => implode( ',', $addTags ), |
526 | | - 'cpc_timestamp' => $dbw->timestamp(), |
527 | | - 'cpc_user' => $user->getId(), |
528 | | - 'cpc_user_text' => $user->getName() |
529 | | - ), |
530 | | - __METHOD__ |
531 | | - ); |
532 | | - } |
533 | | - } |
534 | | - |
535 | | - protected function normalizeTags( $tags ) { |
536 | | - $out = array(); |
537 | | - foreach ( $tags as $tag ) { |
538 | | - $out[] = $this->normalizeTag( $tag ); |
539 | | - } |
540 | | - return $out; |
541 | | - } |
542 | | - |
543 | | - protected function tagData( $tags ) { |
544 | | - $data = array(); |
545 | | - foreach ( $tags as $tag ) { |
546 | | - $data[] = array( |
547 | | - 'ct_repo_id' => $this->mRepoId, |
548 | | - 'ct_rev_id' => $this->mId, |
549 | | - 'ct_tag' => $this->normalizeTag( $tag ) ); |
550 | | - } |
551 | | - return $data; |
552 | | - } |
553 | | - |
554 | | - public function normalizeTag( $tag ) { |
555 | | - global $wgContLang; |
556 | | - $lower = $wgContLang->lc( $tag ); |
557 | | - |
558 | | - $title = Title::newFromText( $tag ); |
559 | | - if ( $title && $lower === $wgContLang->lc( $title->getPrefixedText() ) ) { |
560 | | - return $lower; |
561 | | - } else { |
562 | | - return false; |
563 | | - } |
564 | | - } |
565 | | - |
566 | | - public function isValidTag( $tag ) { |
567 | | - return ( $this->normalizeTag( $tag ) !== false ); |
568 | | - } |
569 | | - |
570 | | - public function getPrevious() { |
571 | | - // hack! |
572 | | - if ( $this->mId > 1 ) { |
573 | | - return $this->mId - 1; |
574 | | - } else { |
575 | | - return false; |
576 | | - } |
577 | | - } |
578 | | - |
579 | | - public function getNext() { |
580 | | - $dbr = wfGetDB( DB_SLAVE ); |
581 | | - $encId = $dbr->addQuotes( $this->mId ); |
582 | | - $row = $dbr->selectRow( 'code_rev', |
583 | | - 'cr_id', |
584 | | - array( |
585 | | - 'cr_repo_id' => $this->mRepoId, |
586 | | - "cr_id > $encId" ), |
587 | | - __METHOD__, |
588 | | - array( |
589 | | - 'ORDER BY' => 'cr_repo_id, cr_id', |
590 | | - 'LIMIT' => 1 ) ); |
591 | | - |
592 | | - if ( $row ) { |
593 | | - return intval( $row->cr_id ); |
594 | | - } else { |
595 | | - return false; |
596 | | - } |
597 | | - } |
598 | | - |
599 | | - public function getNextUnresolved() { |
600 | | - $dbr = wfGetDB( DB_SLAVE ); |
601 | | - $encId = $dbr->addQuotes( $this->mId ); |
602 | | - $row = $dbr->selectRow( 'code_rev', |
603 | | - 'cr_id', |
604 | | - array( |
605 | | - 'cr_repo_id' => $this->mRepoId, |
606 | | - "cr_id > $encId", |
607 | | - 'cr_status' => array( 'new', 'fixme' ) ), |
608 | | - __METHOD__, |
609 | | - array( |
610 | | - 'ORDER BY' => 'cr_repo_id, cr_id', |
611 | | - 'LIMIT' => 1 ) |
612 | | - ); |
613 | | - if ( $row ) { |
614 | | - return intval( $row->cr_id ); |
615 | | - } else { |
616 | | - return false; |
617 | | - } |
618 | | - } |
619 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/CodeRevisionAuthorLink.php |
— | — | @@ -1,89 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -// Special:Code/MediaWiki/author/johndoe/link |
5 | | - |
6 | | -class CodeRevisionAuthorLink extends CodeRevisionAuthorView { |
7 | | - function __construct( $repoName, $author ) { |
8 | | - global $wgRequest; |
9 | | - parent::__construct( $repoName, $author ); |
10 | | - $this->mTarget = $wgRequest->getVal( 'linktouser' ); |
11 | | - } |
12 | | - |
13 | | - function getTitle() { |
14 | | - $repo = $this->mRepo->getName(); |
15 | | - $auth = $this->mAuthor; |
16 | | - return SpecialPage::getTitleFor( 'Code', "$repo/author/$auth/link" ); |
17 | | - } |
18 | | - |
19 | | - function execute() { |
20 | | - global $wgOut, $wgRequest, $wgUser; |
21 | | - if ( !$wgUser->isAllowed( 'codereview-link-user' ) ) { |
22 | | - $wgOut->permissionRequired( 'codereview-link-user' ); |
23 | | - return; |
24 | | - } |
25 | | - if ( $wgRequest->wasPosted() ) { |
26 | | - $this->doSubmit(); |
27 | | - } else { |
28 | | - $this->doForm(); |
29 | | - } |
30 | | - } |
31 | | - |
32 | | - function doForm() { |
33 | | - global $wgOut; |
34 | | - $form = Xml::openElement( 'form', array( 'method' => 'post', |
35 | | - 'action' => $this->getTitle()->getLocalUrl(), |
36 | | - 'name' => 'uluser', 'id' => 'mw-codeauthor-form1' ) ); |
37 | | - $form .= Xml::openElement( 'fieldset' ); |
38 | | - |
39 | | - $additional = ''; |
40 | | - // Is there already a user linked to this author? |
41 | | - if ( $this->mUser ) { |
42 | | - $form .= Xml::element( 'legend', array(), wfMsg( 'code-author-alterlink' ) ); |
43 | | - $additional = Xml::openElement( 'fieldset' ) . |
44 | | - Xml::element( 'legend', array(), wfMsg( 'code-author-orunlink' ) ) . |
45 | | - Xml::submitButton( wfMsg( 'code-author-unlink' ), array( 'name' => 'unlink' ) ) . |
46 | | - Xml::closeElement( 'fieldset' ); |
47 | | - } else { |
48 | | - $form .= Xml::element( 'legend', array(), wfMsg( 'code-author-dolink' ) ); |
49 | | - } |
50 | | - |
51 | | - $form .= Xml::inputLabel( wfMsg( 'code-author-name' ), 'linktouser', 'username', 30, '' ) . ' ' . |
52 | | - Xml::submitButton( wfMsg( 'ok' ), array( 'name' => 'newname' ) ) . |
53 | | - Xml::closeElement( 'fieldset' ) . |
54 | | - $additional . |
55 | | - Xml::closeElement( 'form' ) . "\n"; |
56 | | - |
57 | | - $wgOut->addHTML( $this->linkStatus() . $form ); |
58 | | - } |
59 | | - |
60 | | - function doSubmit() { |
61 | | - global $wgOut, $wgRequest; |
62 | | - // Link an author to a wiki user |
63 | | - if ( strlen( $this->mTarget ) && $wgRequest->getCheck( 'newname' ) ) { |
64 | | - $user = User::newFromName( $this->mTarget, false ); |
65 | | - if ( !$user || !$user->getId() ) { |
66 | | - $wgOut->addWikiMsg( 'nosuchusershort', $this->mTarget ); |
67 | | - return; |
68 | | - } |
69 | | - $this->mRepo->linkUser( $this->mAuthor, $user ); |
70 | | - $userlink = $this->mSkin->userLink( $user->getId(), $user->getName() ); |
71 | | - $wgOut->addHTML( |
72 | | - '<div class="successbox">' . |
73 | | - wfMsgHtml( 'code-author-success', $this->authorLink( $this->mAuthor ), $userlink ) . |
74 | | - '</div>' |
75 | | - ); |
76 | | - // Unlink an author to a wiki users |
77 | | - } else if ( $wgRequest->getVal( 'unlink' ) ) { |
78 | | - if ( !$this->mUser ) { |
79 | | - $wgOut->addHTML( wfMsg( 'code-author-orphan' ) ); |
80 | | - return; |
81 | | - } |
82 | | - $this->mRepo->unlinkUser( $this->mAuthor ); |
83 | | - $wgOut->addHTML( |
84 | | - '<div class="successbox">' . |
85 | | - wfMsgHtml( 'code-author-unlinksuccess', $this->authorLink( $this->mAuthor ) ) . |
86 | | - '</div>' |
87 | | - ); |
88 | | - } |
89 | | - } |
90 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/CodeComment.php |
— | — | @@ -1,29 +0,0 @@ |
2 | | -<?php |
3 | | -if ( !defined( 'MEDIAWIKI' ) ) die(); |
4 | | - |
5 | | -class CodeComment { |
6 | | - function __construct( $rev ) { |
7 | | - $this->rev = $rev; |
8 | | - } |
9 | | - |
10 | | - static function newFromRow( $rev, $row ) { |
11 | | - return self::newFromData( $rev, get_object_vars( $row ) ); |
12 | | - } |
13 | | - |
14 | | - static function newFromData( $rev, $data ) { |
15 | | - $comment = new CodeComment( $rev ); |
16 | | - $comment->id = intval( $data['cc_id'] ); |
17 | | - $comment->text = $data['cc_text']; // fixme |
18 | | - $comment->user = $data['cc_user']; |
19 | | - $comment->userText = $data['cc_user_text']; |
20 | | - $comment->timestamp = wfTimestamp( TS_MW, $data['cc_timestamp'] ); |
21 | | - $comment->review = $data['cc_review']; |
22 | | - $comment->sortkey = $data['cc_sortkey']; |
23 | | - return $comment; |
24 | | - } |
25 | | - |
26 | | - function threadDepth() { |
27 | | - $timestamps = explode( ",", $this->sortkey ); |
28 | | - return count( $timestamps ); |
29 | | - } |
30 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/Subversion.php |
— | — | @@ -1,326 +0,0 @@ |
2 | | -<?php |
3 | | -if ( !defined( 'MEDIAWIKI' ) ) die(); |
4 | | - |
5 | | -abstract class SubversionAdaptor { |
6 | | - protected $mRepo; |
7 | | - |
8 | | - public static function newFromRepo( $repo ) { |
9 | | - global $wgSubversionProxy, $wgSubversionProxyTimeout; |
10 | | - if ( $wgSubversionProxy ) { |
11 | | - return new SubversionProxy( $repo, $wgSubversionProxy, $wgSubversionProxyTimeout ); |
12 | | - } elseif ( function_exists( 'svn_log' ) ) { |
13 | | - return new SubversionPecl( $repo ); |
14 | | - } else { |
15 | | - return new SubversionShell( $repo ); |
16 | | - } |
17 | | - } |
18 | | - |
19 | | - function __construct( $repo ) { |
20 | | - $this->mRepo = $repo; |
21 | | - } |
22 | | - |
23 | | - abstract function getFile( $path, $rev = null ); |
24 | | - |
25 | | - abstract function getDiff( $path, $rev1, $rev2 ); |
26 | | - |
27 | | - abstract function getDirList( $path, $rev = null ); |
28 | | - |
29 | | - /* |
30 | | - array of array( |
31 | | - 'rev' => 123, |
32 | | - 'author' => 'myname', |
33 | | - 'msg' => 'log message' |
34 | | - 'date' => '8601 date', |
35 | | - 'paths' => array( |
36 | | - array( |
37 | | - 'action' => one of M, A, D, R |
38 | | - 'path' => repo URL of file, |
39 | | - ), |
40 | | - ... |
41 | | - ) |
42 | | - */ |
43 | | - abstract function getLog( $path, $startRev = null, $endRev = null ); |
44 | | - |
45 | | - protected function _rev( $rev, $default ) { |
46 | | - if ( $rev === null ) { |
47 | | - return $default; |
48 | | - } else { |
49 | | - return intval( $rev ); |
50 | | - } |
51 | | - } |
52 | | -} |
53 | | - |
54 | | -/** |
55 | | - * Using the SVN PECL extension... |
56 | | - */ |
57 | | -class SubversionPecl extends SubversionAdaptor { |
58 | | - function getFile( $path, $rev = null ) { |
59 | | - return svn_cat( $this->mRepo . $path, $rev ); |
60 | | - } |
61 | | - |
62 | | - function getDiff( $path, $rev1, $rev2 ) { |
63 | | - list( $fout, $ferr ) = svn_diff( |
64 | | - $this->mRepo . $path, $rev1, |
65 | | - $this->mRepo . $path, $rev2 ); |
66 | | - |
67 | | - if ( $fout ) { |
68 | | - // We have to read out the file descriptors. :P |
69 | | - $out = ''; |
70 | | - while ( !feof( $fout ) ) { |
71 | | - $out .= fgets( $fout ); |
72 | | - } |
73 | | - fclose( $fout ); |
74 | | - fclose( $ferr ); |
75 | | - |
76 | | - return $out; |
77 | | - } else { |
78 | | - return new MWException( "Diffing error" ); |
79 | | - } |
80 | | - } |
81 | | - |
82 | | - function getDirList( $path, $rev = null ) { |
83 | | - return svn_ls( $this->mRepo . $path, |
84 | | - $this->_rev( $rev, SVN_REVISION_HEAD ) ); |
85 | | - } |
86 | | - |
87 | | - function getLog( $path, $startRev = null, $endRev = null ) { |
88 | | - return svn_log( $this->mRepo . $path, |
89 | | - $this->_rev( $startRev, SVN_REVISION_INITIAL ), |
90 | | - $this->_rev( $endRev, SVN_REVISION_HEAD ) ); |
91 | | - } |
92 | | -} |
93 | | - |
94 | | -/** |
95 | | - * Using the thingy-bobber |
96 | | - */ |
97 | | -class SubversionShell extends SubversionAdaptor { |
98 | | - function getFile( $path, $rev = null ) { |
99 | | - if ( $rev ) |
100 | | - $path .= "@$rev"; |
101 | | - $command = sprintf( |
102 | | - "svn cat --non-interactive %s %s", |
103 | | - $this->getExtraArgs(), |
104 | | - wfEscapeShellArg( $this->mRepo . $path ) ); |
105 | | - |
106 | | - return wfShellExec( $command ); |
107 | | - } |
108 | | - |
109 | | - function getDiff( $path, $rev1, $rev2 ) { |
110 | | - $command = sprintf( |
111 | | - "svn diff -r%d:%d --non-interactive %s %s", |
112 | | - intval( $rev1 ), |
113 | | - intval( $rev2 ), |
114 | | - $this->getExtraArgs(), |
115 | | - wfEscapeShellArg( $this->mRepo . $path ) ); |
116 | | - |
117 | | - return wfShellExec( $command ); |
118 | | - } |
119 | | - |
120 | | - function getLog( $path, $startRev = null, $endRev = null ) { |
121 | | - $lang = wfIsWindows() ? "" : "LC_ALL=en_US.utf-8 "; |
122 | | - $command = sprintf( |
123 | | - "{$lang}svn log -v -r%s:%s --non-interactive %s %s", |
124 | | - wfEscapeShellArg( $this->_rev( $startRev, 'BASE' ) ), |
125 | | - wfEscapeShellArg( $this->_rev( $endRev, 'HEAD' ) ), |
126 | | - $this->getExtraArgs(), |
127 | | - wfEscapeShellArg( $this->mRepo . $path ) ); |
128 | | - |
129 | | - $lines = explode( "\n", wfShellExec( $command ) ); |
130 | | - $out = array(); |
131 | | - |
132 | | - $divider = str_repeat( '-', 72 ); |
133 | | - $formats = array( |
134 | | - 'rev' => '/^r(\d+)$/', |
135 | | - 'author' => '/^(.*)$/', |
136 | | - 'date' => '/^(.*?) \(.*\)$/', |
137 | | - 'lines' => '/^(\d+) lines?$/', |
138 | | - ); |
139 | | - $state = "start"; |
140 | | - foreach ( $lines as $line ) { |
141 | | - $line = rtrim( $line ); |
142 | | - |
143 | | - switch( $state ) { |
144 | | - case "start": |
145 | | - if ( $line == $divider ) { |
146 | | - $state = "revdata"; |
147 | | - break; |
148 | | - } else { |
149 | | - return $out; |
150 | | - # throw new MWException( "Unexpected start line: $line" ); |
151 | | - } |
152 | | - case "revdata": |
153 | | - if ( $line == "" ) { |
154 | | - $state = "done"; |
155 | | - break; |
156 | | - } |
157 | | - $data = array(); |
158 | | - $bits = explode( " | ", $line ); |
159 | | - $i = 0; |
160 | | - foreach ( $formats as $key => $regex ) { |
161 | | - $text = $bits[$i++]; |
162 | | - if ( preg_match( $regex, $text, $matches ) ) { |
163 | | - $data[$key] = $matches[1]; |
164 | | - } else { |
165 | | - throw new MWException( |
166 | | - "Unexpected format for $key in '$text'" ); |
167 | | - } |
168 | | - } |
169 | | - $data['msg'] = ''; |
170 | | - $data['paths'] = array(); |
171 | | - $state = 'changedpaths'; |
172 | | - break; |
173 | | - case "changedpaths": |
174 | | - if ( $line == "Changed paths:" ) { // broken when svn messages are not in English |
175 | | - $state = "path"; |
176 | | - } elseif ( $line == "" ) { |
177 | | - // No changed paths? |
178 | | - $state = "msg"; |
179 | | - } else { |
180 | | - throw new MWException( |
181 | | - "Expected 'Changed paths:' or '', got '$line'" ); |
182 | | - } |
183 | | - break; |
184 | | - case "path": |
185 | | - if ( $line == "" ) { |
186 | | - // Out of paths. Move on to the message... |
187 | | - $state = 'msg'; |
188 | | - } else { |
189 | | - if ( preg_match( '/^ (.) (.*)$/', $line, $matches ) ) { |
190 | | - $data['paths'][] = array( |
191 | | - 'action' => $matches[1], |
192 | | - 'path' => $matches[2] ); |
193 | | - } |
194 | | - } |
195 | | - break; |
196 | | - case "msg": |
197 | | - $data['msg'] .= $line; |
198 | | - if ( --$data['lines'] ) { |
199 | | - $data['msg'] .= "\n"; |
200 | | - } else { |
201 | | - unset( $data['lines'] ); |
202 | | - $out[] = $data; |
203 | | - $state = "start"; |
204 | | - } |
205 | | - break; |
206 | | - case "done": |
207 | | - throw new MWException( "Unexpected input after end: $line" ); |
208 | | - default: |
209 | | - throw new MWException( "Invalid state '$state'" ); |
210 | | - } |
211 | | - } |
212 | | - |
213 | | - return $out; |
214 | | - } |
215 | | - |
216 | | - function getDirList( $path, $rev = null ) { |
217 | | - $command = sprintf( |
218 | | - "svn list --xml -r%s --non-interactive %s %s", |
219 | | - wfEscapeShellArg( $this->_rev( $rev, 'HEAD' ) ), |
220 | | - $this->getExtraArgs(), |
221 | | - wfEscapeShellArg( $this->mRepo . $path ) ); |
222 | | - $document = new DOMDocument(); |
223 | | - |
224 | | - if ( !@$document->loadXML( wfShellExec( $command ) ) ) |
225 | | - // svn list --xml returns invalid XML if the file does not exist |
226 | | - // FIXME: report bug upstream |
227 | | - return false; |
228 | | - |
229 | | - $entries = $document->getElementsByTagName( 'entry' ); |
230 | | - $result = array(); |
231 | | - foreach ( $entries as $entry ) { |
232 | | - $item = array(); |
233 | | - $item['type'] = $entry->getAttribute( 'kind' ); |
234 | | - foreach ( $entry->childNodes as $child ) { |
235 | | - switch ( $child->nodeName ) { |
236 | | - case 'name': |
237 | | - $item['name'] = $child->textContent; |
238 | | - break; |
239 | | - case 'size': |
240 | | - $item['size'] = intval( $child->textContent ); |
241 | | - break; |
242 | | - case 'commit': |
243 | | - $item['created_rev'] = intval( $child->getAttribute( 'revision' ) ); |
244 | | - foreach ( $child->childNodes as $commitEntry ) { |
245 | | - switch ( $commitEntry->nodeName ) { |
246 | | - case 'author': |
247 | | - $item['last_author'] = $commitEntry->textContent; |
248 | | - break; |
249 | | - case 'date': |
250 | | - $item['time_t'] = wfTimestamp( TS_UNIX, $commitEntry->textContent ); |
251 | | - break; |
252 | | - } |
253 | | - } |
254 | | - break; |
255 | | - } |
256 | | - } |
257 | | - $result[] = $item; |
258 | | - } |
259 | | - return $result; |
260 | | - } |
261 | | - |
262 | | - /** |
263 | | - * Returns a string of extra arguments to be passed into the shell commands |
264 | | - */ |
265 | | - private function getExtraArgs() { |
266 | | - global $wgSubversionUser, $wgSubversionPassword; |
267 | | - if ( $wgSubversionUser ) { |
268 | | - return '--username ' . wfEscapeShellArg( $wgSubversionUser ) |
269 | | - . ' --password ' . wfEscapeShellArg( $wgSubversionPassword ); |
270 | | - } |
271 | | - return ''; |
272 | | - } |
273 | | -} |
274 | | - |
275 | | -/** |
276 | | - * Using a remote JSON proxy |
277 | | - */ |
278 | | -class SubversionProxy extends SubversionAdaptor { |
279 | | - function __construct( $repo, $proxy, $timeout = 30 ) { |
280 | | - parent::__construct( $repo ); |
281 | | - $this->mProxy = $proxy; |
282 | | - $this->mTimeout = $timeout; |
283 | | - } |
284 | | - |
285 | | - function getFile( $path, $rev = null ) { |
286 | | - throw new MWException( "NYI" ); |
287 | | - } |
288 | | - |
289 | | - function getDiff( $path, $rev1, $rev2 ) { |
290 | | - return $this->_proxy( array( |
291 | | - 'action' => 'diff', |
292 | | - 'path' => $path, |
293 | | - 'rev1' => $rev1, |
294 | | - 'rev2' => $rev2 ) ); |
295 | | - } |
296 | | - |
297 | | - function getLog( $path, $startRev = null, $endRev = null ) { |
298 | | - return $this->_proxy( array( |
299 | | - 'action' => 'log', |
300 | | - 'path' => $path, |
301 | | - 'start' => $startRev, |
302 | | - 'end' => $endRev ) ); |
303 | | - } |
304 | | - |
305 | | - function getDirList( $path, $rev = null ) { |
306 | | - return $this->_proxy( array( |
307 | | - 'action' => 'list', |
308 | | - 'path' => $path, |
309 | | - 'rev' => $rev ) ); |
310 | | - } |
311 | | - |
312 | | - protected function _proxy( $params ) { |
313 | | - foreach ( $params as $key => $val ) { |
314 | | - if ( is_null( $val ) ) { |
315 | | - // Don't pass nulls to remote |
316 | | - unset( $params[$key] ); |
317 | | - } |
318 | | - } |
319 | | - $target = $this->mProxy . '?' . wfArrayToCgi( $params ); |
320 | | - $blob = Http::get( $target, $this->mTimeout ); |
321 | | - if ( $blob === false ) { |
322 | | - throw new MWException( "SVN proxy error" ); |
323 | | - } |
324 | | - $data = unserialize( $blob ); |
325 | | - return $data; |
326 | | - } |
327 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/CodeStatusListView.php |
— | — | @@ -1,20 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -// Special:Code/MediaWiki/status |
5 | | -class CodeStatusListView extends CodeView { |
6 | | - function __construct( $repoName ) { |
7 | | - parent::__construct(); |
8 | | - $this->mRepo = CodeRepository::newFromName( $repoName ); |
9 | | - } |
10 | | - |
11 | | - function execute() { |
12 | | - global $wgOut; |
13 | | - $name = $this->mRepo->getName(); |
14 | | - $states = CodeRevision::getPossibleStates(); |
15 | | - $text = "== " . wfMsg ( "code-field-status" ) . " ==\n"; |
16 | | - foreach ( $states as $state ) { |
17 | | - $text .= "* [[Special:Code/$name/status/$state|" . wfMsg ( "code-status-" . $state ) . "]]\n"; |
18 | | - } |
19 | | - $wgOut->addWikiText( $text ); |
20 | | - } |
21 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/CodeCommentsListView.php |
— | — | @@ -1,113 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -// Special:Code/MediaWiki |
5 | | -class CodeCommentsListView extends CodeView { |
6 | | - public $mRepo; |
7 | | - |
8 | | - function __construct( $repoName ) { |
9 | | - parent::__construct(); |
10 | | - $this->mRepo = CodeRepository::newFromName( $repoName ); |
11 | | - } |
12 | | - |
13 | | - function execute() { |
14 | | - global $wgOut; |
15 | | - $pager = $this->getPager(); |
16 | | - $wgOut->addHTML( |
17 | | - $pager->getNavigationBar() . |
18 | | - $pager->getLimitForm() . |
19 | | - $pager->getBody() . |
20 | | - $pager->getNavigationBar() |
21 | | - ); |
22 | | - } |
23 | | - |
24 | | - function getPager() { |
25 | | - return new CodeCommentsTablePager( $this ); |
26 | | - } |
27 | | - function getRepo() { |
28 | | - return $this->mRepo; |
29 | | - } |
30 | | -} |
31 | | - |
32 | | -// Pager for CodeRevisionListView |
33 | | -class CodeCommentsTablePager extends TablePager { |
34 | | - |
35 | | - function __construct( CodeCommentsListView $view ) { |
36 | | - global $IP; |
37 | | - $this->mView = $view; |
38 | | - $this->mRepo = $view->mRepo; |
39 | | - $this->mDefaultDirection = true; |
40 | | - $this->mCurSVN = SpecialVersion::getSvnRevision( $IP ); |
41 | | - parent::__construct(); |
42 | | - } |
43 | | - |
44 | | - function isFieldSortable( $field ) { |
45 | | - return $field == 'cr_timestamp'; |
46 | | - } |
47 | | - |
48 | | - function getDefaultSort() { return 'cc_timestamp'; } |
49 | | - |
50 | | - function getQueryInfo() { |
51 | | - return array( |
52 | | - 'tables' => array( 'code_comment', 'code_rev' ), |
53 | | - 'fields' => array_keys( $this->getFieldNames() ), |
54 | | - 'conds' => array( 'cc_repo_id' => $this->mRepo->getId() ), |
55 | | - 'join_conds' => array( |
56 | | - 'code_rev' => array( 'LEFT JOIN', 'cc_repo_id = cr_repo_id AND cc_rev_id = cr_id' ) |
57 | | - ) |
58 | | - ); |
59 | | - } |
60 | | - |
61 | | - function getFieldNames() { |
62 | | - return array( |
63 | | - 'cc_timestamp' => wfMsg( 'code-field-timestamp' ), |
64 | | - 'cc_user_text' => wfMsg( 'code-field-user' ), |
65 | | - 'cc_rev_id' => wfMsg( 'code-field-id' ), |
66 | | - 'cr_status' => wfMsg( 'code-field-status' ), |
67 | | - 'cr_message' => wfMsg( 'code-field-message' ), |
68 | | - 'cc_text' => wfMsg( 'code-field-text' ), |
69 | | - ); |
70 | | - } |
71 | | - |
72 | | - function formatValue( $name, $value ) { |
73 | | - global $wgUser, $wgLang; |
74 | | - switch( $name ) { |
75 | | - case 'cc_rev_id': |
76 | | - return $this->mView->mSkin->link( |
77 | | - SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $value . '#code-comments' ), |
78 | | - htmlspecialchars( $value ) ); |
79 | | - case 'cr_status': |
80 | | - return $this->mView->mSkin->link( |
81 | | - SpecialPage::getTitleFor( 'Code', |
82 | | - $this->mRepo->getName() . '/status/' . $value ), |
83 | | - htmlspecialchars( $this->mView->statusDesc( $value ) ) ); |
84 | | - case 'cc_user_text': |
85 | | - return $this->mView->mSkin->userLink( -1, $value ); |
86 | | - case 'cr_message': |
87 | | - return $this->mView->messageFragment( $value ); |
88 | | - case 'cc_text': |
89 | | - # Truncate this, blah blah... |
90 | | - return htmlspecialchars( $wgLang->truncate( $value, 300 ) ); |
91 | | - case 'cc_timestamp': |
92 | | - global $wgLang; |
93 | | - return $wgLang->timeanddate( $value, true ); |
94 | | - } |
95 | | - } |
96 | | - |
97 | | - // Note: this function is poorly factored in the parent class |
98 | | - function formatRow( $row ) { |
99 | | - global $wgWikiSVN; |
100 | | - $class = "mw-codereview-status-{$row->cr_status}"; |
101 | | - if ( $this->mRepo->mName == $wgWikiSVN ) { |
102 | | - $class .= " mw-codereview-" . ( $row->cc_rev_id <= $this->mCurSVN ? 'live' : 'notlive' ); |
103 | | - } |
104 | | - return str_replace( |
105 | | - '<tr>', |
106 | | - Xml::openElement( 'tr', |
107 | | - array( 'class' => $class ) ), |
108 | | - parent::formatRow( $row ) ); |
109 | | - } |
110 | | - |
111 | | - function getTitle() { |
112 | | - return SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/comments' ); |
113 | | - } |
114 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/ApiCodeComments.php |
— | — | @@ -1,139 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -/* |
5 | | - * Created on Oct 29, 2008 |
6 | | - * |
7 | | - * API for MediaWiki 1.8+ |
8 | | - * |
9 | | - * Copyright (C) 2008 Bryan Tong Minh <Bryan.TongMinh@Gmail.com> |
10 | | - * |
11 | | - * This program is free software; you can redistribute it and/or modify |
12 | | - * it under the terms of the GNU General Public License as published by |
13 | | - * the Free Software Foundation; either version 2 of the License, or |
14 | | - * (at your option) any later version. |
15 | | - * |
16 | | - * This program is distributed in the hope that it will be useful, |
17 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
18 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
19 | | - * GNU General Public License for more details. |
20 | | - * |
21 | | - * You should have received a copy of the GNU General Public License along |
22 | | - * with this program; if not, write to the Free Software Foundation, Inc., |
23 | | - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
24 | | - * http://www.gnu.org/copyleft/gpl.html |
25 | | - */ |
26 | | - |
27 | | -class ApiCodeComments extends ApiQueryBase { |
28 | | - public function __construct( $query, $moduleName ) { |
29 | | - parent::__construct( $query, $moduleName, 'cc' ); |
30 | | - } |
31 | | - |
32 | | - public function execute() { |
33 | | - global $wgUser; |
34 | | - // Before doing anything at all, let's check permissions |
35 | | - if( !$wgUser->isAllowed('codereview-use') ) { |
36 | | - $this->dieUsage('You don\'t have permission to view code comments','permissiondenied'); |
37 | | - } |
38 | | - $params = $this->extractRequestParams(); |
39 | | - if ( is_null( $params['repo'] ) ) |
40 | | - $this->dieUsageMsg( array( 'missingparam', 'repo' ) ); |
41 | | - $this->props = array_flip( $params['prop'] ); |
42 | | - |
43 | | - $listview = new CodeCommentsListView( $params['repo'] ); |
44 | | - if ( is_null( $listview->getRepo() ) ) |
45 | | - $this->dieUsage( "Invalid repo ``{$params['repo']}''", 'invalidrepo' ); |
46 | | - $pager = $listview->getPager(); |
47 | | - |
48 | | - if ( !is_null( $params['start'] ) ) |
49 | | - $pager->setOffset( $this->getDB()->timestamp( $params['start'] ) ); |
50 | | - $limit = $params['limit']; |
51 | | - $pager->setLimit( $limit ); |
52 | | - |
53 | | - $pager->doQuery(); |
54 | | - |
55 | | - $comments = $pager->getResult(); |
56 | | - $data = array(); |
57 | | - |
58 | | - $count = 0; |
59 | | - $lastTimestamp = 0; |
60 | | - while ( $row = $comments->fetchObject() ) { |
61 | | - if ( $count == $limit ) { |
62 | | - $this->setContinueEnumParameter( 'start', |
63 | | - wfTimestamp( TS_ISO_8601, $lastTimestamp ) ); |
64 | | - break; |
65 | | - } |
66 | | - |
67 | | - $data[] = $this->formatRow( $row ); |
68 | | - $lastTimestamp = $row->cc_timestamp; |
69 | | - $count++; |
70 | | - } |
71 | | - $comments->free(); |
72 | | - |
73 | | - $result = $this->getResult(); |
74 | | - $result->setIndexedTagName( $data, 'comment' ); |
75 | | - $result->addValue( 'query', $this->getModuleName(), $data ); |
76 | | - } |
77 | | - |
78 | | - private function formatRow( $row ) { |
79 | | - $item = array(); |
80 | | - if ( isset( $this->props['timestamp'] ) ) |
81 | | - $item['timestamp'] = wfTimestamp( TS_ISO_8601, $row->cc_timestamp ); |
82 | | - if ( isset( $this->props['user'] ) ) |
83 | | - $item['user'] = $row->cc_user_text; |
84 | | - if ( isset( $this->props['revision'] ) ) |
85 | | - $item['status'] = $row->cr_status; |
86 | | - if ( isset( $this->props['text'] ) ) |
87 | | - ApiResult::setContent( $item, $row->cc_text ); |
88 | | - return $item; |
89 | | - } |
90 | | - |
91 | | - public function getAllowedParams() { |
92 | | - return array ( |
93 | | - 'repo' => null, |
94 | | - 'limit' => array ( |
95 | | - ApiBase :: PARAM_DFLT => 10, |
96 | | - ApiBase :: PARAM_TYPE => 'limit', |
97 | | - ApiBase :: PARAM_MIN => 1, |
98 | | - ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, |
99 | | - ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 |
100 | | - ), |
101 | | - 'start' => array( |
102 | | - ApiBase :: PARAM_TYPE => 'timestamp' |
103 | | - ), |
104 | | - 'prop' => array ( |
105 | | - ApiBase :: PARAM_ISMULTI => true, |
106 | | - ApiBase :: PARAM_DFLT => 'timestamp|user|revision', |
107 | | - ApiBase :: PARAM_TYPE => array ( |
108 | | - 'timestamp', |
109 | | - 'user', |
110 | | - 'revision', |
111 | | - 'text', |
112 | | - ), |
113 | | - ), |
114 | | - ); |
115 | | - } |
116 | | - |
117 | | - public function getParamDescription() { |
118 | | - return array( |
119 | | - 'repo' => 'Name of the repository', |
120 | | - 'limit' => 'How many comments to return', |
121 | | - 'start' => 'Timestamp to start listing at', |
122 | | - 'prop' => 'Which properties to return', |
123 | | - ); |
124 | | - } |
125 | | - |
126 | | - public function getDescription() { |
127 | | - return 'List comments on revisions in CodeReview'; |
128 | | - } |
129 | | - |
130 | | - public function getExamples() { |
131 | | - return array( |
132 | | - 'api.php?action=query&list=codecomments&ccrepo=MediaWiki', |
133 | | - 'api.php?action=query&list=codecomments&ccrepo=MediaWiki&ccprop=timestamp|user|revision|text', |
134 | | - ); |
135 | | - } |
136 | | - |
137 | | - public function getVersion() { |
138 | | - return __CLASS__ . ': $Id$'; |
139 | | - } |
140 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/CodeRepository.php |
— | — | @@ -1,339 +0,0 @@ |
2 | | -<?php |
3 | | -if ( !defined( 'MEDIAWIKI' ) ) die(); |
4 | | - |
5 | | -class CodeRepository { |
6 | | - static $userLinks = array(); |
7 | | - |
8 | | - public static function newFromName( $name ) { |
9 | | - $dbw = wfGetDB( DB_MASTER ); |
10 | | - $row = $dbw->selectRow( |
11 | | - 'code_repo', |
12 | | - array( |
13 | | - 'repo_id', |
14 | | - 'repo_name', |
15 | | - 'repo_path', |
16 | | - 'repo_viewvc', |
17 | | - 'repo_bugzilla' ), |
18 | | - array( 'repo_name' => $name ), |
19 | | - __METHOD__ ); |
20 | | - |
21 | | - if ( $row ) { |
22 | | - return self::newFromRow( $row ); |
23 | | - } else { |
24 | | - return null; |
25 | | - } |
26 | | - } |
27 | | - |
28 | | - public static function newFromId( $id ) { |
29 | | - $dbw = wfGetDB( DB_MASTER ); |
30 | | - $row = $dbw->selectRow( |
31 | | - 'code_repo', |
32 | | - array( |
33 | | - 'repo_id', |
34 | | - 'repo_name', |
35 | | - 'repo_path', |
36 | | - 'repo_viewvc', |
37 | | - 'repo_bugzilla' ), |
38 | | - array( 'repo_id' => intval( $id ) ), |
39 | | - __METHOD__ ); |
40 | | - |
41 | | - if ( $row ) { |
42 | | - return self::newFromRow( $row ); |
43 | | - } else { |
44 | | - return null; |
45 | | - } |
46 | | - } |
47 | | - |
48 | | - static function newFromRow( $row ) { |
49 | | - $repo = new CodeRepository(); |
50 | | - $repo->mId = intval( $row->repo_id ); |
51 | | - $repo->mName = $row->repo_name; |
52 | | - $repo->mPath = $row->repo_path; |
53 | | - $repo->mViewVc = $row->repo_viewvc; |
54 | | - $repo->mBugzilla = $row->repo_bugzilla; |
55 | | - return $repo; |
56 | | - } |
57 | | - |
58 | | - static function getRepoList() { |
59 | | - $dbr = wfGetDB( DB_SLAVE ); |
60 | | - $res = $dbr->select( 'code_repo', '*', array(), __METHOD__ ); |
61 | | - $repos = array(); |
62 | | - foreach ( $res as $row ) { |
63 | | - $repos[] = self::newFromRow( $row ); |
64 | | - } |
65 | | - return $repos; |
66 | | - } |
67 | | - |
68 | | - public function getId() { |
69 | | - return intval( $this->mId ); |
70 | | - } |
71 | | - |
72 | | - public function getName() { |
73 | | - return $this->mName; |
74 | | - } |
75 | | - |
76 | | - public function getPath() { |
77 | | - return $this->mPath; |
78 | | - } |
79 | | - |
80 | | - public function getViewVcBase() { |
81 | | - return $this->mViewVc; |
82 | | - } |
83 | | - |
84 | | - /** |
85 | | - * Return a bug URL or false. |
86 | | - */ |
87 | | - public function getBugPath( $bugId ) { |
88 | | - if ( $this->mBugzilla ) { |
89 | | - return str_replace( '$1', |
90 | | - urlencode( $bugId ), $this->mBugzilla ); |
91 | | - } |
92 | | - return false; |
93 | | - } |
94 | | - |
95 | | - public function getLastStoredRev() { |
96 | | - $dbr = wfGetDB( DB_SLAVE ); |
97 | | - $row = $dbr->selectField( |
98 | | - 'code_rev', |
99 | | - 'MAX(cr_id)', |
100 | | - array( 'cr_repo_id' => $this->getId() ), |
101 | | - __METHOD__ |
102 | | - ); |
103 | | - return intval( $row ); |
104 | | - } |
105 | | - |
106 | | - public function getAuthorList() { |
107 | | - global $wgMemc; |
108 | | - $key = wfMemcKey( 'codereview', 'authors', $this->getId() ); |
109 | | - $authors = $wgMemc->get( $key ); |
110 | | - if ( is_array( $authors ) ) { |
111 | | - return $authors; |
112 | | - } |
113 | | - $dbr = wfGetDB( DB_SLAVE ); |
114 | | - $res = $dbr->select( |
115 | | - 'code_rev', |
116 | | - array( 'cr_author', 'MAX(cr_timestamp) AS time' ), |
117 | | - array( 'cr_repo_id' => $this->getId() ), |
118 | | - __METHOD__, |
119 | | - array( 'GROUP BY' => 'cr_author', |
120 | | - 'ORDER BY' => 'time DESC', 'LIMIT' => 500 ) |
121 | | - ); |
122 | | - $authors = array(); |
123 | | - while ( $row = $dbr->fetchObject( $res ) ) { |
124 | | - $authors[] = $row->cr_author; |
125 | | - } |
126 | | - $wgMemc->set( $key, $authors, 3600 * 24 * 3 ); |
127 | | - return $authors; |
128 | | - } |
129 | | - |
130 | | - public function getTagList() { |
131 | | - global $wgMemc; |
132 | | - $key = wfMemcKey( 'codereview', 'tags', $this->getId() ); |
133 | | - $tags = $wgMemc->get( $key ); |
134 | | - if ( is_array( $tags ) ) { |
135 | | - return $tags; |
136 | | - } |
137 | | - $dbr = wfGetDB( DB_SLAVE ); |
138 | | - $res = $dbr->select( |
139 | | - 'code_tags', |
140 | | - array( 'ct_tag', 'COUNT(*) AS revs' ), |
141 | | - array( 'ct_repo_id' => $this->getId() ), |
142 | | - __METHOD__, |
143 | | - array( 'GROUP BY' => 'ct_tag', |
144 | | - 'ORDER BY' => 'revs DESC', 'LIMIT' => 500 ) |
145 | | - ); |
146 | | - $tags = array(); |
147 | | - while ( $row = $dbr->fetchObject( $res ) ) { |
148 | | - $tags[] = $row->ct_tag; |
149 | | - } |
150 | | - $wgMemc->set( $key, $tags, 3600 * 24 * 3 ); |
151 | | - return $tags; |
152 | | - } |
153 | | - |
154 | | - /** |
155 | | - * Load a particular revision out of the DB |
156 | | - */ |
157 | | - public function getRevision( $id ) { |
158 | | - if ( !$this->isValidRev( $id ) ) { |
159 | | - return null; |
160 | | - } |
161 | | - $dbr = wfGetDB( DB_SLAVE ); |
162 | | - $row = $dbr->selectRow( 'code_rev', |
163 | | - '*', |
164 | | - array( |
165 | | - 'cr_id' => $id, |
166 | | - 'cr_repo_id' => $this->getId(), |
167 | | - ), |
168 | | - __METHOD__ |
169 | | - ); |
170 | | - if ( !$row ) |
171 | | - throw new MWException( 'Failed to load expected revision data' ); |
172 | | - return CodeRevision::newFromRow( $this, $row ); |
173 | | - } |
174 | | - |
175 | | - /** |
176 | | - * @param int $rev Revision ID |
177 | | - * @param $useCache 'skipcache' to avoid caching |
178 | | - * 'cached' to *only* fetch if cached |
179 | | - */ |
180 | | - public function getDiff( $rev, $useCache = '' ) { |
181 | | - global $wgMemc; |
182 | | - wfProfileIn( __METHOD__ ); |
183 | | - |
184 | | - $rev1 = $rev - 1; |
185 | | - $rev2 = $rev; |
186 | | - |
187 | | - $revision = $this->getRevision( $rev ); |
188 | | - if ( $revision == null || !$revision->isDiffable() ) { |
189 | | - wfProfileOut( __METHOD__ ); |
190 | | - return false; |
191 | | - } |
192 | | - |
193 | | - # Try memcached... |
194 | | - $key = wfMemcKey( 'svn', md5( $this->mPath ), 'diff', $rev1, $rev2 ); |
195 | | - if ( $useCache === 'skipcache' ) { |
196 | | - $data = NULL; |
197 | | - } else { |
198 | | - $data = $wgMemc->get( $key ); |
199 | | - } |
200 | | - |
201 | | - # Try the database... |
202 | | - if ( !$data && $useCache != 'skipcache' ) { |
203 | | - $dbr = wfGetDB( DB_SLAVE ); |
204 | | - $row = $dbr->selectRow( 'code_rev', |
205 | | - array( 'cr_diff', 'cr_flags' ), |
206 | | - array( 'cr_repo_id' => $this->mId, 'cr_id' => $rev, 'cr_diff IS NOT NULL' ), |
207 | | - __METHOD__ |
208 | | - ); |
209 | | - if ( $row ) { |
210 | | - $flags = explode( ',', $row->cr_flags ); |
211 | | - $data = $row->cr_diff; |
212 | | - // If the text was fetched without an error, convert it |
213 | | - if ( $data !== false && in_array( 'gzip', $flags ) ) { |
214 | | - # Deal with optional compression of archived pages. |
215 | | - # This can be done periodically via maintenance/compressOld.php, and |
216 | | - # as pages are saved if $wgCompressRevisions is set. |
217 | | - $data = gzinflate( $data ); |
218 | | - } |
219 | | - } |
220 | | - } |
221 | | - |
222 | | - # Generate the diff as needed... |
223 | | - if ( !$data && $useCache !== 'cached' ) { |
224 | | - $svn = SubversionAdaptor::newFromRepo( $this->mPath ); |
225 | | - $data = $svn->getDiff( '', $rev1, $rev2 ); |
226 | | - // Store to cache |
227 | | - $wgMemc->set( $key, $data, 3600 * 24 * 3 ); |
228 | | - // Permanent DB storage |
229 | | - $storedData = $data; |
230 | | - $flags = Revision::compressRevisionText( $storedData ); |
231 | | - $dbw = wfGetDB( DB_MASTER ); |
232 | | - $dbw->update( 'code_rev', |
233 | | - array( 'cr_diff' => $storedData, 'cr_flags' => $flags ), |
234 | | - array( 'cr_repo_id' => $this->mId, 'cr_id' => $rev ), |
235 | | - __METHOD__ |
236 | | - ); |
237 | | - } |
238 | | - wfProfileOut( __METHOD__ ); |
239 | | - return $data; |
240 | | - } |
241 | | - |
242 | | - /** |
243 | | - * Is the requested revid a valid revision to show? |
244 | | - * @return bool |
245 | | - * @param $rev int Rev id to check |
246 | | - */ |
247 | | - public function isValidRev( $rev ) { |
248 | | - $rev = intval( $rev ); |
249 | | - if ( $rev > 0 && $rev <= $this->getLastStoredRev() ) { |
250 | | - return true; |
251 | | - } |
252 | | - return false; |
253 | | - } |
254 | | - |
255 | | - /* |
256 | | - * Link the $author to the wikiuser $user |
257 | | - * @param string $author |
258 | | - * @param User $user |
259 | | - * @return bool success |
260 | | - */ |
261 | | - public function linkUser( $author, User $user ) { |
262 | | - // We must link to an existing user |
263 | | - if ( !$user->getId() ) { |
264 | | - return false; |
265 | | - } |
266 | | - $dbw = wfGetDB( DB_MASTER ); |
267 | | - // Insert in the auther -> user link row. |
268 | | - // Skip existing rows. |
269 | | - $dbw->insert( 'code_authors', |
270 | | - array( |
271 | | - 'ca_repo_id' => $this->getId(), |
272 | | - 'ca_author' => $author, |
273 | | - 'ca_user_text' => $user->getName() |
274 | | - ), |
275 | | - __METHOD__, |
276 | | - array( 'IGNORE' ) |
277 | | - ); |
278 | | - // If the last query already found a row, then update it. |
279 | | - if ( !$dbw->affectedRows() ) { |
280 | | - $dbw->update( |
281 | | - 'code_authors', |
282 | | - array( 'ca_user_text' => $user->getName() ), |
283 | | - array( |
284 | | - 'ca_repo_id' => $this->getId(), |
285 | | - 'ca_author' => $author, |
286 | | - ), |
287 | | - __METHOD__ |
288 | | - ); |
289 | | - } |
290 | | - self::$userLinks[$author] = $user; |
291 | | - return ( $dbw->affectedRows() > 0 ); |
292 | | - } |
293 | | - |
294 | | - /* |
295 | | - * Remove local user links for $author |
296 | | - * @param string $author |
297 | | - * @return bool success |
298 | | - */ |
299 | | - public function unlinkUser( $author ) { |
300 | | - $dbw = wfGetDB( DB_MASTER ); |
301 | | - $dbw->delete( |
302 | | - 'code_authors', |
303 | | - array( |
304 | | - 'ca_repo_id' => $this->getId(), |
305 | | - 'ca_author' => $author, |
306 | | - ), |
307 | | - __METHOD__ |
308 | | - ); |
309 | | - self::$userLinks[$author] = false; |
310 | | - return ( $dbw->affectedRows() > 0 ); |
311 | | - } |
312 | | - |
313 | | - /* |
314 | | - * returns a User object if $author has a wikiuser associated, |
315 | | - * or false |
316 | | - */ |
317 | | - public function authorWikiUser( $author ) { |
318 | | - if ( isset( self::$userLinks[$author] ) ) |
319 | | - return self::$userLinks[$author]; |
320 | | - |
321 | | - $dbr = wfGetDB( DB_SLAVE ); |
322 | | - $wikiUser = $dbr->selectField( |
323 | | - 'code_authors', |
324 | | - 'ca_user_text', |
325 | | - array( |
326 | | - 'ca_repo_id' => $this->getId(), |
327 | | - 'ca_author' => $author, |
328 | | - ), |
329 | | - __METHOD__ |
330 | | - ); |
331 | | - $user = null; |
332 | | - if ( $wikiUser ) |
333 | | - $user = User::newFromName( $wikiUser ); |
334 | | - if ( $user instanceof User ) |
335 | | - $res = $user; |
336 | | - else |
337 | | - $res = false; |
338 | | - return self::$userLinks[$author] = $res; |
339 | | - } |
340 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/SpecialCode.php |
— | — | @@ -1,237 +0,0 @@ |
2 | | -<?php |
3 | | -if ( !defined( 'MEDIAWIKI' ) ) die(); |
4 | | - |
5 | | -class SpecialCode extends SpecialPage { |
6 | | - function __construct() { |
7 | | - parent::__construct( 'Code' , 'codereview-use' ); |
8 | | - } |
9 | | - |
10 | | - function execute( $subpage ) { |
11 | | - global $wgOut, $wgRequest, $wgUser, $wgScriptPath, $wgCodeReviewStyleVersion; |
12 | | - |
13 | | - wfLoadExtensionMessages( 'CodeReview' ); |
14 | | - |
15 | | - if( !$this->userCanExecute( $wgUser ) ) { |
16 | | - $this->displayRestrictionError(); |
17 | | - return; |
18 | | - } |
19 | | - |
20 | | - $this->setHeaders(); |
21 | | - $wgOut->addStyle( "$wgScriptPath/extensions/CodeReview/codereview.css?$wgCodeReviewStyleVersion" ); |
22 | | - # Remove stray slashes |
23 | | - $subpage = preg_replace( '/\/$/', '', $subpage ); |
24 | | - |
25 | | - if ( $subpage == '' ) { |
26 | | - $view = new CodeRepoListView(); |
27 | | - } else { |
28 | | - $params = explode( '/', $subpage ); |
29 | | - switch( count( $params ) ) { |
30 | | - case 1: |
31 | | - $view = new CodeRevisionListView( $params[0] ); |
32 | | - break; |
33 | | - case 2: |
34 | | - if ( $params[1] === 'tag' ) { |
35 | | - $view = new CodeTagListView( $params[0] ); |
36 | | - break; |
37 | | - } elseif ( $params[1] === 'author' ) { |
38 | | - $view = new CodeAuthorListView( $params[0] ); |
39 | | - break; |
40 | | - } elseif ( $params[1] === 'status' ) { |
41 | | - $view = new CodeStatusListView( $params[0] ); |
42 | | - break; |
43 | | - } elseif ( $params[1] === 'comments' ) { |
44 | | - $view = new CodeCommentsListView( $params[0] ); |
45 | | - break; |
46 | | - } elseif ( $params[1] === 'releasenotes' ) { |
47 | | - $view = new CodeReleaseNotes( $params[0] ); |
48 | | - break; |
49 | | - } else if ( $wgRequest->wasPosted() && !$wgRequest->getCheck( 'wpPreview' ) ) { |
50 | | - # Add any tags, Set status, Adds comments |
51 | | - $submit = new CodeRevisionCommitter( $params[0], $params[1] ); |
52 | | - $submit->execute(); |
53 | | - return; |
54 | | - } else { // revision details |
55 | | - $view = new CodeRevisionView( $params[0], $params[1] ); |
56 | | - break; |
57 | | - } |
58 | | - case 3: |
59 | | - if ( $params[1] === 'tag' ) { |
60 | | - $view = new CodeRevisionTagView( $params[0], $params[2] ); |
61 | | - break; |
62 | | - } elseif ( $params[1] === 'author' ) { |
63 | | - $view = new CodeRevisionAuthorView( $params[0], $params[2] ); |
64 | | - break; |
65 | | - } elseif ( $params[1] === 'status' ) { |
66 | | - $view = new CodeRevisionStatusView( $params[0], $params[2] ); |
67 | | - break; |
68 | | - } elseif ( $params[1] === 'comments' ) { |
69 | | - $view = new CodeCommentsListView( $params[0] ); |
70 | | - break; |
71 | | - } else { |
72 | | - # Nonsense parameters, back out |
73 | | - if ( empty( $params[1] ) ) |
74 | | - $view = new CodeRevisionListView( $params[0] ); |
75 | | - else |
76 | | - $view = new CodeRevisionView( $params[0], $params[1] ); |
77 | | - break; |
78 | | - } |
79 | | - case 4: |
80 | | - if ( $params[1] == 'author' && $params[3] == 'link' ) { |
81 | | - $view = new CodeRevisionAuthorLink( $params[0], $params[2] ); |
82 | | - break; |
83 | | - } |
84 | | - default: |
85 | | - if ( $params[2] == 'reply' ) { |
86 | | - $view = new CodeRevisionView( $params[0], $params[1], $params[3] ); |
87 | | - break; |
88 | | - } |
89 | | - $wgOut->addWikiText( wfMsg( 'nosuchactiontext' ) ); |
90 | | - $wgOut->returnToMain( null, SpecialPage::getTitleFor( 'Code' ) ); |
91 | | - return; |
92 | | - } |
93 | | - } |
94 | | - $view->execute(); |
95 | | - |
96 | | - // Add subtitle for easy navigation |
97 | | - global $wgOut; |
98 | | - if ( $view instanceof CodeView && ( $repo = $view->getRepo() ) ) { |
99 | | - $wgOut->setSubtitle( wfMsgExt( 'codereview-subtitle', 'parse', CodeRepoListView::getNavItem( $repo->getName() ) ) ); |
100 | | - } |
101 | | - } |
102 | | -} |
103 | | - |
104 | | -/** |
105 | | - * Extended by CodeRevisionListView and CodeRevisionView |
106 | | - */ |
107 | | -abstract class CodeView { |
108 | | - var $mRepo; |
109 | | - |
110 | | - function __construct() { |
111 | | - global $wgUser; |
112 | | - $this->mSkin = $wgUser->getSkin(); |
113 | | - } |
114 | | - |
115 | | - function validPost( $permission ) { |
116 | | - global $wgRequest, $wgUser; |
117 | | - return $wgRequest->wasPosted() |
118 | | - && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) |
119 | | - && $wgUser->isAllowed( $permission ); |
120 | | - } |
121 | | - |
122 | | - abstract function execute(); |
123 | | - |
124 | | - /* |
125 | | - * returns a User object if $author has a wikiuser associated, |
126 | | - * of false |
127 | | - */ |
128 | | - function authorWikiUser( $author ) { |
129 | | - return $this->mRepo->authorWikiUser( $author ); |
130 | | - } |
131 | | - |
132 | | - function authorLink( $author ) { |
133 | | - $repo = $this->mRepo->getName(); |
134 | | - $special = SpecialPage::getTitleFor( 'Code', "$repo/author/$author" ); |
135 | | - return $this->mSkin->link( $special, htmlspecialchars( $author ) ); |
136 | | - } |
137 | | - |
138 | | - function statusDesc( $status ) { |
139 | | - return wfMsg( "code-status-$status" ); |
140 | | - } |
141 | | - |
142 | | - function formatMessage( $text ) { |
143 | | - $text = nl2br( htmlspecialchars( $text ) ); |
144 | | - $linker = new CodeCommentLinkerHtml( $this->mRepo ); |
145 | | - return $linker->link( $text ); |
146 | | - } |
147 | | - |
148 | | - function messageFragment( $value ) { |
149 | | - global $wgLang; |
150 | | - $message = trim( $value ); |
151 | | - $lines = explode( "\n", $message, 2 ); |
152 | | - $first = $lines[0]; |
153 | | - $trimmed = $wgLang->truncate( $first, 80 ); |
154 | | - return $this->formatMessage( $trimmed ); |
155 | | - } |
156 | | - /* |
157 | | - * Formatted HTML array for properties display |
158 | | - * @param array fields : 'propname' => HTML data |
159 | | - */ |
160 | | - function formatMetaData( $fields ) { |
161 | | - $html = '<table class="mw-codereview-meta">'; |
162 | | - foreach ( $fields as $label => $data ) { |
163 | | - $html .= "<tr><td>" . wfMsgHtml( $label ) . "</td><td>$data</td></tr>\n"; |
164 | | - } |
165 | | - return $html . "</table>\n"; |
166 | | - } |
167 | | - |
168 | | - function getRepo() { |
169 | | - if ( $this->mRepo ) |
170 | | - return $this->mRepo; |
171 | | - return false; |
172 | | - } |
173 | | -} |
174 | | - |
175 | | -class CodeCommentLinker { |
176 | | - function __construct( $repo ) { |
177 | | - global $wgUser; |
178 | | - $this->mSkin = $wgUser->getSkin(); |
179 | | - $this->mRepo = $repo; |
180 | | - } |
181 | | - |
182 | | - function link( $text ) { |
183 | | - # Catch links like http://www.mediawiki.org/wiki/Special:Code/MediaWiki/44245#c829 |
184 | | - # Ended by space or brackets (like those pesky <br/> tags) |
185 | | - $text = preg_replace_callback( '/(\b)(' . wfUrlProtocols() . ')([^ <>]+)(\b)/', array( $this, 'generalLink' ), $text ); |
186 | | - $text = preg_replace_callback( '/\br(\d+)\b/', array( $this, 'messageRevLink' ), $text ); |
187 | | - $text = preg_replace_callback( '/\bbug #?(\d+)\b/i', array( $this, 'messageBugLink' ), $text ); |
188 | | - return $text; |
189 | | - } |
190 | | - |
191 | | - function generalLink( $arr ) { |
192 | | - $url = $arr[2] . $arr[3]; |
193 | | - // Re-add the surrounding space/punctuation |
194 | | - return $arr[1] . $this->makeExternalLink( $url, $url ) . $arr[4]; |
195 | | - } |
196 | | - |
197 | | - function messageBugLink( $arr ) { |
198 | | - $text = $arr[0]; |
199 | | - $bugNo = intval( $arr[1] ); |
200 | | - $url = $this->mRepo->getBugPath( $bugNo ); |
201 | | - if ( $url ) { |
202 | | - return $this->makeExternalLink( $url, $text ); |
203 | | - } else { |
204 | | - return $text; |
205 | | - } |
206 | | - } |
207 | | - |
208 | | - function messageRevLink( $matches ) { |
209 | | - $text = $matches[0]; |
210 | | - $rev = intval( $matches[1] ); |
211 | | - |
212 | | - $repo = $this->mRepo->getName(); |
213 | | - $title = SpecialPage::getTitleFor( 'Code', "$repo/$rev" ); |
214 | | - |
215 | | - return $this->makeInternalLink( $title, $text ); |
216 | | - } |
217 | | - |
218 | | -} |
219 | | - |
220 | | -class CodeCommentLinkerHtml extends CodeCommentLinker { |
221 | | - function makeExternalLink( $url, $text ) { |
222 | | - return $this->mSkin->makeExternalLink( $url, $text ); |
223 | | - } |
224 | | - |
225 | | - function makeInternalLink( $title, $text ) { |
226 | | - return $this->mSkin->link( $title, $text ); |
227 | | - } |
228 | | -} |
229 | | - |
230 | | -class CodeCommentLinkerWiki extends CodeCommentLinker { |
231 | | - function makeExternalLink( $url, $text ) { |
232 | | - return "[$url $text]"; |
233 | | - } |
234 | | - |
235 | | - function makeInternalLink( $title, $text ) { |
236 | | - return "[[" . $title->getPrefixedText() . "|$text]]"; |
237 | | - } |
238 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/SpecialRepoAdmin.php |
— | — | @@ -1,123 +0,0 @@ |
2 | | -<?php |
3 | | -if ( !defined( 'MEDIAWIKI' ) ) die(); |
4 | | - |
5 | | -class SpecialRepoAdmin extends SpecialPage { |
6 | | - function __construct() { |
7 | | - parent::__construct( 'RepoAdmin', 'repoadmin' ); |
8 | | - } |
9 | | - |
10 | | - function execute( $subpage ) { |
11 | | - global $wgRequest, $wgUser; |
12 | | - |
13 | | - wfLoadExtensionMessages( 'CodeReview' ); |
14 | | - $this->setHeaders(); |
15 | | - |
16 | | - if ( !$this->userCanExecute( $wgUser ) ) { |
17 | | - $this->displayRestrictionError(); |
18 | | - return; |
19 | | - } |
20 | | - |
21 | | - $repo = $wgRequest->getVal( 'repo', $subpage ); |
22 | | - if ( $repo == '' ) { |
23 | | - $view = new RepoAdminListView( $this ); |
24 | | - } else { |
25 | | - $view = new RepoAdminRepoView( $this, $repo ); |
26 | | - } |
27 | | - $view->execute(); |
28 | | - } |
29 | | -} |
30 | | - |
31 | | -class RepoAdminListView { |
32 | | - var $mPage; |
33 | | - |
34 | | - function __construct( $page ) { |
35 | | - $this->mPage = $page; |
36 | | - } |
37 | | - |
38 | | - function getForm() { |
39 | | - global $wgScript; |
40 | | - return Xml::fieldset( wfMsg( 'repoadmin-new-legend' ) ) . |
41 | | - Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . |
42 | | - Xml::hidden( 'title', $this->mPage->getTitle()->getPrefixedDBKey() ) . |
43 | | - Xml::inputLabel( wfMsg( 'repoadmin-new-label' ), 'repo', 'repo' ) . |
44 | | - Xml::submitButton( wfMsg( 'repoadmin-new-button' ) ) . |
45 | | - '</form></fieldset>'; |
46 | | - } |
47 | | - |
48 | | - function execute() { |
49 | | - global $wgOut; |
50 | | - $wgOut->addHTML( $this->getForm() ); |
51 | | - $repos = CodeRepository::getRepoList(); |
52 | | - if ( !count( $repos ) ) { |
53 | | - return; |
54 | | - } |
55 | | - $text = ''; |
56 | | - foreach ( $repos as $repo ) { |
57 | | - $name = $repo->getName(); |
58 | | - $text .= "* [[Special:RepoAdmin/$name|$name]]\n"; |
59 | | - } |
60 | | - $wgOut->addWikiText( $text ); |
61 | | - } |
62 | | -} |
63 | | - |
64 | | -class RepoAdminRepoView { |
65 | | - var $mPage; |
66 | | - |
67 | | - function __construct( $page, $repo ) { |
68 | | - $this->mPage = $page; |
69 | | - $this->mRepoName = $repo; |
70 | | - $this->mRepo = CodeRepository::newFromName( $repo ); |
71 | | - } |
72 | | - |
73 | | - function execute() { |
74 | | - global $wgOut, $wgRequest, $wgUser; |
75 | | - $repoExists = (bool)$this->mRepo; |
76 | | - $repoPath = $wgRequest->getVal( 'wpRepoPath', $repoExists ? $this->mRepo->mPath : '' ); |
77 | | - $bugPath = $wgRequest->getVal( 'wpBugPath', $repoExists ? $this->mRepo->mBugzilla : '' ); |
78 | | - $viewPath = $wgRequest->getVal( 'wpViewPath', $repoExists ? $this->mRepo->mViewVc : '' ); |
79 | | - if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ), $this->mRepoName ) ) { |
80 | | - // @todo log |
81 | | - $dbw = wfGetDB( DB_MASTER ); |
82 | | - if ( $repoExists ) { |
83 | | - $dbw->update( |
84 | | - 'code_repo', |
85 | | - array( |
86 | | - 'repo_path' => $repoPath, |
87 | | - 'repo_viewvc' => $viewPath, |
88 | | - 'repo_bugzilla' => $bugPath |
89 | | - ), |
90 | | - array( 'repo_id' => $this->mRepo->getId() ), |
91 | | - __METHOD__ |
92 | | - ); |
93 | | - } else { |
94 | | - $dbw->insert( |
95 | | - 'code_repo', |
96 | | - array( |
97 | | - 'repo_name' => $this->mRepoName, |
98 | | - 'repo_path' => $repoPath, |
99 | | - 'repo_viewvc' => $viewPath, |
100 | | - 'repo_bugzilla' => $bugPath |
101 | | - ), |
102 | | - __METHOD__ |
103 | | - ); |
104 | | - } |
105 | | - $wgOut->wrapWikiMsg( '<div class="successbox">$1</div>', array( 'repoadmin-edit-sucess', $this->mRepoName ) ); |
106 | | - return; |
107 | | - } |
108 | | - $wgOut->addHTML( |
109 | | - Xml::fieldset( wfMsg( 'repoadmin-edit-legend', $this->mRepoName ) ) . |
110 | | - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->mPage->getTitle( $this->mRepoName )->getLocalURL() ) ) . |
111 | | - Xml::buildForm( |
112 | | - array( |
113 | | - 'repoadmin-edit-path' => |
114 | | - Xml::input( 'wpRepoPath', 60, $repoPath ), |
115 | | - 'repoadmin-edit-bug' => |
116 | | - Xml::input( 'wpBugPath', 60, $bugPath ), |
117 | | - 'repoadmin-edit-view' => |
118 | | - Xml::input( 'wpViewPath', 60, $viewPath ) ) ) . |
119 | | - Xml::hidden( 'wpEditToken', $wgUser->editToken( $this->mRepoName ) ) . |
120 | | - Xml::submitButton( wfMsg( 'repoadmin-edit-button' ) ) . |
121 | | - '</form></fieldset>' |
122 | | - ); |
123 | | - } |
124 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/CodeRevisionTagView.php |
— | — | @@ -1,33 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -class CodeRevisionTagView extends CodeRevisionListView { |
5 | | - function __construct( $repoName, $tag ) { |
6 | | - parent::__construct( $repoName ); |
7 | | - $this->mTag = $tag; |
8 | | - } |
9 | | - |
10 | | - function getPager() { |
11 | | - return new SvnRevTagTablePager( $this, $this->mTag ); |
12 | | - } |
13 | | -} |
14 | | - |
15 | | -class SvnRevTagTablePager extends SvnRevTablePager { |
16 | | - function __construct( $view, $tag ) { |
17 | | - parent::__construct( $view ); |
18 | | - $this->mTag = $tag; |
19 | | - } |
20 | | - |
21 | | - function getQueryInfo() { |
22 | | - $info = parent::getQueryInfo(); |
23 | | - $info['tables'][] = 'code_tags'; |
24 | | - $info['conds'][] = 'cr_repo_id=ct_repo_id'; |
25 | | - $info['conds'][] = 'cr_id=ct_rev_id'; |
26 | | - $info['conds']['ct_tag'] = $this->mTag; // fixme: normalize input? |
27 | | - return $info; |
28 | | - } |
29 | | - |
30 | | - function getTitle() { |
31 | | - $repo = $this->mRepo->getName(); |
32 | | - return SpecialPage::getTitleFor( 'Code', "$repo/tag/$this->mTag" ); |
33 | | - } |
34 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/CodePropChange.php |
— | — | @@ -1,24 +0,0 @@ |
2 | | -<?php |
3 | | -if ( !defined( 'MEDIAWIKI' ) ) die(); |
4 | | - |
5 | | -class CodePropChange { |
6 | | - function __construct( $rev ) { |
7 | | - $this->rev = $rev; |
8 | | - } |
9 | | - |
10 | | - static function newFromRow( $rev, $row ) { |
11 | | - return self::newFromData( $rev, get_object_vars( $row ) ); |
12 | | - } |
13 | | - |
14 | | - static function newFromData( $rev, $data ) { |
15 | | - $change = new CodeComment( $rev ); |
16 | | - $change->attrib = $data['cpc_attrib']; |
17 | | - $change->removed = $data['cpc_removed']; |
18 | | - $change->added = $data['cpc_added']; |
19 | | - $change->user = $data['cpc_user']; |
20 | | - // We'd prefer the up to date user table name |
21 | | - $change->userText = isset( $data['user_name'] ) ? $data['user_name'] : $data['cpc_user_text']; |
22 | | - $change->timestamp = wfTimestamp( TS_MW, $data['cpc_timestamp'] ); |
23 | | - return $change; |
24 | | - } |
25 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/CodeAuthorListView.php |
— | — | @@ -1,21 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -// Special:Code/MediaWiki/author |
5 | | -class CodeAuthorListView extends CodeView { |
6 | | - function __construct( $repoName ) { |
7 | | - parent::__construct(); |
8 | | - $this->mRepo = CodeRepository::newFromName( $repoName ); |
9 | | - } |
10 | | - |
11 | | - function execute() { |
12 | | - global $wgOut; |
13 | | - $authors = $this->mRepo->getAuthorList(); |
14 | | - $name = $this->mRepo->getName(); |
15 | | - $text = wfMsg( 'code-authors-text' ) . "\n"; |
16 | | - foreach ( $authors as $user ) { |
17 | | - if ( $user ) |
18 | | - $text .= "* [[Special:Code/$name/author/$user|$user]]\n"; |
19 | | - } |
20 | | - $wgOut->addWikiText( $text ); |
21 | | - } |
22 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/DiffHighlighter.php |
— | — | @@ -1,64 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -/** |
5 | | - * Highlight a SVN diff for easier readibility |
6 | | - */ |
7 | | -class CodeDiffHighlighter { |
8 | | - |
9 | | - /** |
10 | | - * Main entry point. Given a diff text, highlight it |
11 | | - * and wrap it in a div |
12 | | - * @param $text string Text to highlight |
13 | | - * @return string |
14 | | - */ |
15 | | - function render( $text ) { |
16 | | - return '<pre class="mw-codereview-diff">' . |
17 | | - $this->splitLines( $text ) . |
18 | | - "</pre>\n"; |
19 | | - } |
20 | | - |
21 | | - /** |
22 | | - * Given a bunch of text, split it into individual |
23 | | - * lines, color them, then put it back into one big |
24 | | - * string |
25 | | - * @param $text string Text to split and highlight |
26 | | - * @return string |
27 | | - */ |
28 | | - function splitLines( $text ) { |
29 | | - return implode( "\n", |
30 | | - array_map( array( $this, 'colorLine' ), |
31 | | - explode( "\n", $text ) ) ); |
32 | | - } |
33 | | - |
34 | | - /** |
35 | | - * Turn a diff line into a properly formatted string suitable |
36 | | - * for output |
37 | | - * @param $line string Line from a diff |
38 | | - * @return string |
39 | | - */ |
40 | | - function colorLine( $line ) { |
41 | | - list( $element, $attribs ) = $this->tagForLine( $line ); |
42 | | - return Xml::element( $element, $attribs, $line ); |
43 | | - } |
44 | | - |
45 | | - /** |
46 | | - * Take a line of a diff and apply the appropriate stylings |
47 | | - * @param $line string Line to check |
48 | | - * @return array |
49 | | - */ |
50 | | - function tagForLine( $line ) { |
51 | | - static $default = array( 'span', array() ); |
52 | | - static $tags = array( |
53 | | - '-' => array( 'del', array() ), |
54 | | - '+' => array( 'ins', array() ), |
55 | | - '@' => array( 'span', array( 'class' => 'meta' ) ), |
56 | | - ' ' => array( 'span', array() ), |
57 | | - ); |
58 | | - $first = substr( $line, 0, 1 ); |
59 | | - if ( isset( $tags[$first] ) ) |
60 | | - return $tags[$first]; |
61 | | - else |
62 | | - return $default; |
63 | | - } |
64 | | - |
65 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/CodeRevisionStatusView.php |
— | — | @@ -1,30 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -class CodeRevisionStatusView extends CodeRevisionListView { |
5 | | - function __construct( $repoName, $status ) { |
6 | | - parent::__construct( $repoName ); |
7 | | - $this->mStatus = $status; |
8 | | - } |
9 | | - |
10 | | - function getPager() { |
11 | | - return new SvnRevStatusTablePager( $this, $this->mStatus ); |
12 | | - } |
13 | | -} |
14 | | - |
15 | | -class SvnRevStatusTablePager extends SvnRevTablePager { |
16 | | - function __construct( $view, $status ) { |
17 | | - parent::__construct( $view ); |
18 | | - $this->mStatus = $status; |
19 | | - } |
20 | | - |
21 | | - function getQueryInfo() { |
22 | | - $info = parent::getQueryInfo(); |
23 | | - $info['conds']['cr_status'] = $this->mStatus; // FIXME: normalize input? |
24 | | - return $info; |
25 | | - } |
26 | | - |
27 | | - function getTitle() { |
28 | | - $repo = $this->mRepo->getName(); |
29 | | - return SpecialPage::getTitleFor( 'Code', "$repo/status/$this->mStatus" ); |
30 | | - } |
31 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/ApiCodeDiff.php |
— | — | @@ -1,80 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -class ApiCodeDiff extends ApiBase { |
5 | | - |
6 | | - public function execute() { |
7 | | - global $wgUser; |
8 | | - // Before doing anything at all, let's check permissions |
9 | | - if( !$wgUser->isAllowed('codereview-use') ) { |
10 | | - $this->dieUsage('You don\'t have permission to view code diffs','permissiondenied'); |
11 | | - } |
12 | | - $params = $this->extractRequestParams(); |
13 | | - |
14 | | - if ( !isset( $params['repo'] ) ) { |
15 | | - $this->dieUsageMsg( array( 'missingparam', 'repo' ) ); |
16 | | - } |
17 | | - if ( !isset( $params['rev'] ) ) { |
18 | | - $this->dieUsageMsg( array( 'missingparam', 'rev' ) ); |
19 | | - } |
20 | | - |
21 | | - $repo = CodeRepository::newFromName( $params['repo'] ); |
22 | | - if ( !$repo ) { |
23 | | - $this->dieUsage( "Invalid repo ``{$params['repo']}''", 'invalidrepo' ); |
24 | | - } |
25 | | - |
26 | | - $svn = SubversionAdaptor::newFromRepo( $repo->getPath() ); |
27 | | - $lastStoredRev = $repo->getLastStoredRev(); |
28 | | - |
29 | | - if ( $params['rev'] > $lastStoredRev ) { |
30 | | - $this->dieUsage( "There is no revision with ID {$params['rev']}", 'nosuchrev' ); |
31 | | - } |
32 | | - |
33 | | - $diff = $repo->getDiff( $params['rev'] ); |
34 | | - |
35 | | - if ( $diff ) { |
36 | | - $hilite = new CodeDiffHighlighter(); |
37 | | - $html = $hilite->render( $diff ); |
38 | | - } else { |
39 | | - // FIXME: Are we sure we don't want to throw an error here? |
40 | | - $html = 'Failed to load diff.'; |
41 | | - } |
42 | | - |
43 | | - $data = array( |
44 | | - 'repo' => $params['repo'], |
45 | | - 'id' => $params['rev'], |
46 | | - 'diff' => $html |
47 | | - ); |
48 | | - $this->getResult()->addValue( 'code', 'rev', $data ); |
49 | | - } |
50 | | - |
51 | | - public function getAllowedParams() { |
52 | | - return array( |
53 | | - 'repo' => null, |
54 | | - 'rev' => array( |
55 | | - ApiBase::PARAM_TYPE => 'integer', |
56 | | - ApiBase::PARAM_MIN => 1 |
57 | | - ) |
58 | | - ); |
59 | | - } |
60 | | - |
61 | | - public function getParamDescription() { |
62 | | - return array( |
63 | | - 'repo' => 'Name of repository to look at', |
64 | | - 'rev' => 'Revision ID to fetch diff of' ); |
65 | | - } |
66 | | - |
67 | | - public function getDescription() { |
68 | | - return array( |
69 | | - 'Fetch formatted diff from CodeReview\'s backing revision control system.' ); |
70 | | - } |
71 | | - |
72 | | - public function getExamples() { |
73 | | - return array( |
74 | | - 'api.php?action=codediff&repo=MediaWiki&rev=42080', |
75 | | - ); |
76 | | - } |
77 | | - |
78 | | - public function getVersion() { |
79 | | - return __CLASS__ . ': $Id$'; |
80 | | - } |
81 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/CodeTagListView.php |
— | — | @@ -1,20 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -// Special:Code/MediaWiki/tag |
5 | | -class CodeTagListView extends CodeView { |
6 | | - function __construct( $repoName ) { |
7 | | - parent::__construct(); |
8 | | - $this->mRepo = CodeRepository::newFromName( $repoName ); |
9 | | - } |
10 | | - |
11 | | - function execute() { |
12 | | - global $wgOut; |
13 | | - $tags = $this->mRepo->getTagList(); |
14 | | - $name = $this->mRepo->getName(); |
15 | | - $text = ''; |
16 | | - foreach ( $tags as $tag ) { |
17 | | - $text .= "* [[Special:Code/$name/tag/$tag|$tag]]\n"; |
18 | | - } |
19 | | - $wgOut->addWikiText( $text ); |
20 | | - } |
21 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/CodeRevisionAuthorView.php |
— | — | @@ -1,65 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -class CodeRevisionAuthorView extends CodeRevisionListView { |
5 | | - function __construct( $repoName, $author ) { |
6 | | - parent::__construct( $repoName ); |
7 | | - $this->mAuthor = $author; |
8 | | - $this->mUser = $this->authorWikiUser( $author ); |
9 | | - } |
10 | | - |
11 | | - function getPager() { |
12 | | - return new SvnRevAuthorTablePager( $this, $this->mAuthor ); |
13 | | - } |
14 | | - |
15 | | - function linkStatus() { |
16 | | - if ( !$this->mUser ) |
17 | | - return wfMsg( 'code-author-orphan' ); |
18 | | - |
19 | | - return wfMsgHtml( 'code-author-haslink', |
20 | | - $this->mSkin->userLink( $this->mUser->getId(), $this->mUser->getName() ) . |
21 | | - $this->mSkin->userToolLinks( $this->mUser->getId(), $this->mUser->getName() ) ); |
22 | | - } |
23 | | - |
24 | | - function execute() { |
25 | | - global $wgOut, $wgUser; |
26 | | - |
27 | | - $linkInfo = $this->linkStatus(); |
28 | | - |
29 | | - if ( $wgUser->isAllowed( 'codereview-link-user' ) ) { |
30 | | - $repo = $this->mRepo->getName(); |
31 | | - $page = SpecialPage::getTitleFor( 'Code', "$repo/author/$this->mAuthor/link" ); |
32 | | - $linkInfo .= ' (' . $this->mSkin->link( $page, |
33 | | - wfMsg( 'code-author-' . ( $this->mUser ? 'un':'' ) . 'link' ) ) . ')' ; |
34 | | - } |
35 | | - |
36 | | - $repoLink = $wgUser->getSkin()->link( SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() ), |
37 | | - htmlspecialchars( $this->mRepo->getName() ) ); |
38 | | - $fields = array( |
39 | | - 'code-rev-repo' => $repoLink, |
40 | | - 'code-rev-author' => $this->authorLink( $this->mAuthor ), |
41 | | - ); |
42 | | - |
43 | | - $wgOut->addHTML( $this->formatMetaData( $fields ) . $linkInfo ); |
44 | | - |
45 | | - parent::execute(); |
46 | | - } |
47 | | - |
48 | | -} |
49 | | - |
50 | | -class SvnRevAuthorTablePager extends SvnRevTablePager { |
51 | | - function __construct( $view, $author ) { |
52 | | - parent::__construct( $view ); |
53 | | - $this->mAuthor = $author; |
54 | | - } |
55 | | - |
56 | | - function getQueryInfo() { |
57 | | - $info = parent::getQueryInfo(); |
58 | | - $info['conds']['cr_author'] = $this->mAuthor; // fixme: normalize input? |
59 | | - return $info; |
60 | | - } |
61 | | - |
62 | | - function getTitle() { |
63 | | - $repo = $this->mRepo->getName(); |
64 | | - return SpecialPage::getTitleFor( 'Code', "$repo/author/$this->mAuthor" ); |
65 | | - } |
66 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/ApiCodeUpdate.php |
— | — | @@ -1,104 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -class ApiCodeUpdate extends ApiBase { |
5 | | - |
6 | | - public function execute() { |
7 | | - global $wgUser; |
8 | | - // Before doing anything at all, let's check permissions |
9 | | - if( !$wgUser->isAllowed('codereview-use') ) { |
10 | | - $this->dieUsage('You don\'t have permission update code','permissiondenied'); |
11 | | - } |
12 | | - $params = $this->extractRequestParams(); |
13 | | - |
14 | | - if ( !isset( $params['repo'] ) ) { |
15 | | - $this->dieUsageMsg( array( 'missingparam', 'repo' ) ); |
16 | | - } |
17 | | - if ( !isset( $params['rev'] ) ) { |
18 | | - $this->dieUsageMsg( array( 'missingparam', 'rev' ) ); |
19 | | - } |
20 | | - |
21 | | - $repo = CodeRepository::newFromName( $params['repo'] ); |
22 | | - if ( !$repo ) { |
23 | | - $this->dieUsage( "Invalid repo ``{$params['repo']}''", 'invalidrepo' ); |
24 | | - } |
25 | | - |
26 | | - $svn = SubversionAdaptor::newFromRepo( $repo->getPath() ); |
27 | | - $lastStoredRev = $repo->getLastStoredRev(); |
28 | | - |
29 | | - if ( $lastStoredRev >= $params['rev'] ) { |
30 | | - // Nothing to do, we're up to date. |
31 | | - // Return an empty result |
32 | | - $this->getResult()->addValue( null, $this->getModuleName(), array() ); |
33 | | - return; |
34 | | - } |
35 | | - |
36 | | - // FIXME: this could be a lot? |
37 | | - $log = $svn->getLog( '', $lastStoredRev + 1, $params['rev'] ); |
38 | | - if ( !$log ) { |
39 | | - // FIXME: When and how often does this happen? |
40 | | - // Should we use dieUsage() here instead? |
41 | | - ApiBase::dieDebug( __METHOD__, "Something awry..." ); |
42 | | - } |
43 | | - |
44 | | - $result = array(); |
45 | | - foreach ( $log as $data ) { |
46 | | - $codeRev = CodeRevision::newFromSvn( $repo, $data ); |
47 | | - $codeRev->save(); |
48 | | - $result[] = array( |
49 | | - 'id' => $codeRev->getId(), |
50 | | - 'author' => $codeRev->getAuthor(), |
51 | | - 'timestamp' => wfTimestamp( TS_ISO_8601, $codeRev->getTimestamp() ), |
52 | | - 'message' => $codeRev->getMessage() |
53 | | - ); |
54 | | - } |
55 | | - // Cache the diffs if there are a only a few. |
56 | | - // Mainly for WMF post-commit ping hook... |
57 | | - if ( count( $result ) <= 2 ) { |
58 | | - foreach ( $result as $revData ) { |
59 | | - $diff = $repo->getDiff( $revData['id'] ); // trigger caching |
60 | | - } |
61 | | - } |
62 | | - $this->getResult()->setIndexedTagName( $result, 'rev' ); |
63 | | - $this->getResult()->addValue( null, $this->getModuleName(), $result ); |
64 | | - } |
65 | | - |
66 | | - public function mustBePosted() { |
67 | | - // Discourage casual browsing :) |
68 | | - return true; |
69 | | - } |
70 | | - |
71 | | - public function isWriteMode() { |
72 | | - return true; |
73 | | - } |
74 | | - |
75 | | - public function getAllowedParams() { |
76 | | - return array( |
77 | | - 'repo' => null, |
78 | | - 'rev' => array( |
79 | | - ApiBase::PARAM_TYPE => 'integer', |
80 | | - ApiBase::PARAM_MIN => 1 |
81 | | - ) |
82 | | - ); |
83 | | - } |
84 | | - |
85 | | - public function getParamDescription() { |
86 | | - return array( |
87 | | - 'repo' => 'Name of repository to update', |
88 | | - 'rev' => 'Revision ID number to update to' ); |
89 | | - } |
90 | | - |
91 | | - public function getDescription() { |
92 | | - return array( |
93 | | - 'Update CodeReview repository data from master revision control system.' ); |
94 | | - } |
95 | | - |
96 | | - public function getExamples() { |
97 | | - return array( |
98 | | - 'api.php?action=codeupdate&repo=MediaWiki&rev=42080', |
99 | | - ); |
100 | | - } |
101 | | - |
102 | | - public function getVersion() { |
103 | | - return __CLASS__ . ': $Id$'; |
104 | | - } |
105 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/CodeRevisionCommitter.php |
— | — | @@ -1,76 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -class CodeRevisionCommitter extends CodeRevisionView { |
5 | | - |
6 | | - function __construct( $repoName, $rev ) { |
7 | | - // Parent should set $this->mRepo, $this->mRev, $this->mReplyTarget |
8 | | - parent::__construct( $repoName, $rev ); |
9 | | - } |
10 | | - |
11 | | - function execute() { |
12 | | - global $wgRequest, $wgOut, $wgUser; |
13 | | - |
14 | | - if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { |
15 | | - $wgOut->addHTML( '<strong>' . wfMsg( 'sessionfailure' ) . '</strong>' ); |
16 | | - parent::execute(); |
17 | | - return; |
18 | | - } |
19 | | - if ( !$this->mRev ) { |
20 | | - parent::execute(); |
21 | | - return; |
22 | | - } |
23 | | - |
24 | | - $redirTarget = null; |
25 | | - $dbw = wfGetDB( DB_MASTER ); |
26 | | - |
27 | | - $dbw->begin(); |
28 | | - // Change the status if allowed |
29 | | - if ( $this->validPost( 'codereview-set-status' ) && $this->mRev->isValidStatus( $this->mStatus ) ) { |
30 | | - $this->mRev->setStatus( $this->mStatus, $wgUser ); |
31 | | - } |
32 | | - $addTags = $removeTags = array(); |
33 | | - if ( $this->validPost( 'codereview-add-tag' ) && count( $this->mAddTags ) ) { |
34 | | - $addTags = $this->mAddTags; |
35 | | - } |
36 | | - if ( $this->validPost( 'codereview-remove-tag' ) && count( $this->mRemoveTags ) ) { |
37 | | - $removeTags = $this->mRemoveTags; |
38 | | - } |
39 | | - // If allowed to change any tags, then do so |
40 | | - if ( count( $addTags ) || count( $removeTags ) ) { |
41 | | - $this->mRev->changeTags( $addTags, $removeTags, $wgUser ); |
42 | | - } |
43 | | - // Add any comments |
44 | | - if ( $this->validPost( 'codereview-post-comment' ) && strlen( $this->text ) ) { |
45 | | - $parent = $wgRequest->getIntOrNull( 'wpParent' ); |
46 | | - $review = $wgRequest->getInt( 'wpReview' ); |
47 | | - $isPreview = $wgRequest->getCheck( 'wpPreview' ); |
48 | | - $id = $this->mRev->saveComment( $this->text, $review, $parent ); |
49 | | - // For comments, take us back to the rev page focused on the new comment |
50 | | - if ( !$this->jumpToNext ) { |
51 | | - $redirTarget = $this->commentLink( $id ); |
52 | | - } |
53 | | - } |
54 | | - $dbw->commit(); |
55 | | - |
56 | | - // Return to rev page |
57 | | - if ( !$redirTitle ) { |
58 | | - if ( $this->jumpToNext ) { |
59 | | - if ( $next = $this->mRev->getNextUnresolved() ) { |
60 | | - $redirTitle = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $next ); |
61 | | - } else { |
62 | | - $redirTitle = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() ); |
63 | | - } |
64 | | - } else { |
65 | | - # $redirTarget already set for comments |
66 | | - $redirTitle = $redirTarget ? $redirTarget : $this->revLink(); |
67 | | - } |
68 | | - } |
69 | | - $wgOut->redirect( $redirTitle->getFullUrl() ); |
70 | | - } |
71 | | - |
72 | | - public function validPost( $permission ) { |
73 | | - global $wgUser, $wgRequest; |
74 | | - return parent::validPost( $permission ) && $wgRequest->wasPosted() |
75 | | - && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ); |
76 | | - } |
77 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/CodeRevisionListView.php |
— | — | @@ -1,300 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -// Special:Code/MediaWiki |
5 | | -class CodeRevisionListView extends CodeView { |
6 | | - public $mRepo, $mPath; |
7 | | - function __construct( $repoName ) { |
8 | | - global $wgRequest; |
9 | | - parent::__construct(); |
10 | | - $this->mRepo = CodeRepository::newFromName( $repoName ); |
11 | | - $this->mPath = htmlspecialchars( trim( $wgRequest->getVal( 'path' ) ) ); |
12 | | - if ( strlen( $this->mPath ) && $this->mPath[0] !== '/' ) { |
13 | | - $this->mPath = "/{$this->mPath}"; // make sure this is a valid path |
14 | | - } |
15 | | - $this->mAuthor = null; |
16 | | - } |
17 | | - |
18 | | - function execute() { |
19 | | - global $wgOut, $wgUser, $wgRequest; |
20 | | - if ( !$this->mRepo ) { |
21 | | - $view = new CodeRepoListView(); |
22 | | - $view->execute(); |
23 | | - return; |
24 | | - } |
25 | | - |
26 | | - // Check for batch change requests. |
27 | | - $editToken = $wgRequest->getVal( 'wpBatchChangeEditToken' ); |
28 | | - if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $editToken ) ) { |
29 | | - $this->doBatchChange(); |
30 | | - return; |
31 | | - } |
32 | | - |
33 | | - $this->showForm(); |
34 | | - $pager = $this->getPager(); |
35 | | - |
36 | | - // Build batch change interface as needed |
37 | | - $this->batchForm = $wgUser->isAllowed( 'codereview-set-status' ) || |
38 | | - $wgUser->isAllowed( 'codereview-add-tag' ); |
39 | | - |
40 | | - $wgOut->addHTML( |
41 | | - $pager->getNavigationBar() . |
42 | | - $pager->getLimitForm() . |
43 | | - Xml::openElement( 'form', |
44 | | - array( 'action' => $pager->getTitle()->getLocalURL(), 'method' => 'post' ) |
45 | | - ) . |
46 | | - $pager->getBody() . |
47 | | - $pager->getNavigationBar() . |
48 | | - ( $this->batchForm ? $this->buildBatchInterface( $pager ) : "" ) . |
49 | | - Xml::closeElement( 'form' ) |
50 | | - ); |
51 | | - } |
52 | | - |
53 | | - function doBatchChange() { |
54 | | - global $wgRequest; |
55 | | - |
56 | | - $revisions = $wgRequest->getArray( 'wpRevisionSelected' ); |
57 | | - $removeTags = $wgRequest->getVal( 'wpRemoveTag' ); |
58 | | - $addTags = $wgRequest->getVal( 'wpTag' ); |
59 | | - $status = $wgRequest->getVal( 'wpStatus' ); |
60 | | - |
61 | | - // Grab data from the DB |
62 | | - $dbr = wfGetDB( DB_SLAVE ); |
63 | | - $revObjects = array(); |
64 | | - $res = $dbr->select( 'code_rev', '*', array( 'cr_id' => $revisions ), __METHOD__ ); |
65 | | - while ( $row = $dbr->fetchObject( $res ) ) { |
66 | | - $revObjects[] = CodeRevision::newFromRow( $this->mRepo, $row ); |
67 | | - } |
68 | | - |
69 | | - global $wgUser; |
70 | | - if ( $wgUser->isAllowed( 'codereview-add-tag' ) && |
71 | | - $addTags || $removeTags ) { |
72 | | - $addTags = array_map( 'trim', explode( ",", $addTags ) ); |
73 | | - $removeTags = array_map( 'trim', explode( ",", $removeTags ) ); |
74 | | - |
75 | | - foreach ( $revObjects as $id => $rev ) { |
76 | | - $rev->changeTags( $addTags, $removeTags, $wgUser ); |
77 | | - } |
78 | | - } |
79 | | - |
80 | | - if ( $wgUser->isAllowed( 'codereview-set-status' ) && |
81 | | - $revObjects[0]->isValidStatus( $status ) ) { |
82 | | - foreach ( $revObjects as $id => $rev ) { |
83 | | - $rev->setStatus( $status, $wgUser ); |
84 | | - } |
85 | | - } |
86 | | - |
87 | | - // Automatically refresh |
88 | | - // This way of getting GET parameters is horrible, but effective. |
89 | | - $fields = array_merge( $_GET, $_POST ); |
90 | | - foreach ( array_keys( $fields ) as $key ) { |
91 | | - if ( substr( $key, 0, 2 ) == 'wp' || $key == 'title' ) |
92 | | - unset( $fields[$key] ); |
93 | | - } |
94 | | - |
95 | | - global $wgOut; |
96 | | - $wgOut->redirect( $this->getPager()->getTitle()->getFullURL( $fields ) ); |
97 | | - } |
98 | | - |
99 | | - protected function buildBatchInterface( $pager ) { |
100 | | - global $wgUser; |
101 | | - |
102 | | - $changeInterface = ''; |
103 | | - $changeFields = array(); |
104 | | - |
105 | | - if ( $wgUser->isAllowed( 'codereview-set-status' ) ) { |
106 | | - $changeFields['code-batch-status'] = |
107 | | - Xml::tags( 'select', array( 'name' => 'wpStatus' ), |
108 | | - Xml::tags( 'option', |
109 | | - array( 'value' => '', 'selected' => 'selected' ), ' ' |
110 | | - ) . |
111 | | - CodeRevisionView::buildStatusList( null, $this ) |
112 | | - ); |
113 | | - } |
114 | | - |
115 | | - if ( $wgUser->isAllowed( 'codereview-add-tag' ) ) { |
116 | | - $changeFields['code-batch-tags'] = CodeRevisionView::addTagForm( '', '' ); |
117 | | - } |
118 | | - |
119 | | - if ( !count( $changeFields ) ) return ''; // nothing to do here |
120 | | - |
121 | | - $changeInterface = Xml::fieldset( wfMsg( 'codereview-batch-title' ), |
122 | | - Xml::buildForm( $changeFields, 'codereview-batch-submit' ) ); |
123 | | - |
124 | | - $changeInterface .= $pager->getHiddenFields(); |
125 | | - $changeInterface .= Xml::hidden( 'wpBatchChangeEditToken', $wgUser->editToken() ); |
126 | | - |
127 | | - return $changeInterface; |
128 | | - } |
129 | | - |
130 | | - function showForm( $path = '' ) { |
131 | | - global $wgOut, $wgScript; |
132 | | - if ( $this->mAuthor ) { |
133 | | - $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/author/' . $this->mAuthor ); |
134 | | - } else { |
135 | | - $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/path' ); |
136 | | - } |
137 | | - $wgOut->addHTML( |
138 | | - Xml::openElement( 'form', array( 'action' => $wgScript, 'method' => 'get' ) ) . |
139 | | - "<fieldset><legend>" . wfMsgHtml( 'code-pathsearch-legend' ) . "</legend>" . |
140 | | - Xml::hidden( 'title', $special->getPrefixedDBKey() ) . |
141 | | - Xml::inputlabel( wfMsg( "code-pathsearch-path" ), 'path', 'path', 55, $this->mPath ) . |
142 | | - ' ' . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . |
143 | | - "</fieldset>" . Xml::closeElement( 'form' ) |
144 | | - ); |
145 | | - } |
146 | | - |
147 | | - function getPager() { |
148 | | - return new SvnRevTablePager( $this ); |
149 | | - } |
150 | | -} |
151 | | - |
152 | | -// Pager for CodeRevisionListView |
153 | | -class SvnRevTablePager extends TablePager { |
154 | | - |
155 | | - function __construct( $view ) { |
156 | | - global $IP; |
157 | | - $this->mView = $view; |
158 | | - $this->mRepo = $view->mRepo; |
159 | | - $this->mDefaultDirection = true; |
160 | | - $this->mCurSVN = SpecialVersion::getSvnRevision( $IP ); |
161 | | - parent::__construct(); |
162 | | - } |
163 | | - |
164 | | - function getSVNPath() { |
165 | | - return $this->mView->mPath; |
166 | | - } |
167 | | - |
168 | | - function isFieldSortable( $field ) { |
169 | | - return $field == $this->getDefaultSort(); |
170 | | - } |
171 | | - |
172 | | - function getDefaultSort() { |
173 | | - return strlen( $this->mView->mPath ) ? 'cp_rev_id' : 'cr_id'; |
174 | | - } |
175 | | - |
176 | | - function getQueryInfo() { |
177 | | - // Path-based query... |
178 | | - if ( $this->getDefaultSort() === 'cp_rev_id' ) { |
179 | | - return array( |
180 | | - 'tables' => array( 'code_paths', 'code_rev', 'code_comment' ), |
181 | | - 'fields' => $this->getSelectFields(), |
182 | | - 'conds' => array( |
183 | | - 'cp_repo_id' => $this->mRepo->getId(), |
184 | | - 'cp_path LIKE ' . $this->mDb->addQuotes( $this->mDb->escapeLike( $this->getSVNPath() ) . '%' ), |
185 | | - // performance |
186 | | - 'cp_rev_id > ' . $this->mRepo->getLastStoredRev() - 20000 |
187 | | - ), |
188 | | - 'options' => array( 'GROUP BY' => 'cp_rev_id', 'USE INDEX' => array( 'code_path' => 'cp_repo_id' ) ), |
189 | | - 'join_conds' => array( |
190 | | - 'code_rev' => array( 'INNER JOIN', 'cr_repo_id = cp_repo_id AND cr_id = cp_rev_id' ), |
191 | | - 'code_comment' => array( 'LEFT JOIN', 'cc_repo_id = cp_repo_id AND cc_rev_id = cp_rev_id' ) |
192 | | - ) |
193 | | - ); |
194 | | - // No path; entire repo... |
195 | | - } else { |
196 | | - return array( |
197 | | - 'tables' => array( 'code_rev', 'code_comment' ), |
198 | | - 'fields' => $this->getSelectFields(), |
199 | | - 'conds' => array( 'cr_repo_id' => $this->mRepo->getId() ), |
200 | | - 'options' => array( 'GROUP BY' => 'cr_id' ), |
201 | | - 'join_conds' => array( |
202 | | - 'code_comment' => array( 'LEFT JOIN', 'cc_repo_id = cr_repo_id AND cc_rev_id = cr_id' ) |
203 | | - ) |
204 | | - ); |
205 | | - } |
206 | | - return false; |
207 | | - } |
208 | | - |
209 | | - function getSelectFields() { |
210 | | - return array( $this->getDefaultSort(), 'cr_status', 'COUNT( DISTINCT cc_id ) AS comments', |
211 | | - 'cr_path', 'cr_message', 'cr_author', 'cr_timestamp' ); |
212 | | - } |
213 | | - |
214 | | - function getFieldNames() { |
215 | | - $fields = array( |
216 | | - $this->getDefaultSort() => wfMsg( 'code-field-id' ), |
217 | | - 'cr_status' => wfMsg( 'code-field-status' ), |
218 | | - 'comments' => wfMsg( 'code-field-comments' ), |
219 | | - 'cr_path' => wfMsg( 'code-field-path' ), |
220 | | - 'cr_message' => wfMsg( 'code-field-message' ), |
221 | | - 'cr_author' => wfMsg( 'code-field-author' ), |
222 | | - 'cr_timestamp' => wfMsg( 'code-field-timestamp' ), |
223 | | - ); |
224 | | - # Only show checkboxen as needed |
225 | | - if ( !empty( $this->mView->batchForm ) ) { |
226 | | - $fields = array( 'selectforchange' => wfMsg( 'code-field-select' ) ) + $fields; |
227 | | - } |
228 | | - return $fields; |
229 | | - } |
230 | | - |
231 | | - function formatValue( $name, $value ) { } // unused |
232 | | - |
233 | | - function formatRevValue( $name, $value, $row ) { |
234 | | - global $wgUser, $wgLang; |
235 | | - switch( $name ) { |
236 | | - case 'selectforchange': |
237 | | - $sort = $this->getDefaultSort(); |
238 | | - return Xml::check( "wpRevisionSelected[]", false, array( 'value' => $row->$sort ) ); |
239 | | - case 'cp_rev_id': |
240 | | - case 'cr_id': |
241 | | - return $this->mView->mSkin->link( |
242 | | - SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $value ), |
243 | | - htmlspecialchars( $value ) ); |
244 | | - case 'cr_status': |
245 | | - return $this->mView->mSkin->link( |
246 | | - SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/status/' . $value ), |
247 | | - htmlspecialchars( $this->mView->statusDesc( $value ) ) |
248 | | - ); |
249 | | - case 'cr_author': |
250 | | - return $this->mView->authorLink( $value ); |
251 | | - case 'cr_message': |
252 | | - return $this->mView->messageFragment( $value ); |
253 | | - case 'cr_timestamp': |
254 | | - global $wgLang; |
255 | | - return $wgLang->timeanddate( $value, true ); |
256 | | - case 'comments': |
257 | | - if ( $value ) { |
258 | | - $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $row-> { $this->getDefaultSort() } ); |
259 | | - $special->setFragment( '#code-comments' ); |
260 | | - return $this->mView->mSkin->link( $special, htmlspecialchars( $value ) ); |
261 | | - } else { |
262 | | - return intval( $value ); |
263 | | - } |
264 | | - case 'cr_path': |
265 | | - return Xml::openElement( 'div', array( 'title' => (string)$value ) ) . |
266 | | - $this->mView->mSkin->link( |
267 | | - SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/path' ), |
268 | | - $wgLang->truncate( (string)$value, 30 ), |
269 | | - array(), |
270 | | - array( 'path' => (string)$value ) ) . "</div>"; |
271 | | - } |
272 | | - } |
273 | | - |
274 | | - // Note: this function is poorly factored in the parent class |
275 | | - function formatRow( $row ) { |
276 | | - global $wgWikiSVN; |
277 | | - $css = "mw-codereview-status-{$row->cr_status}"; |
278 | | - if ( $this->mRepo->mName == $wgWikiSVN ) { |
279 | | - $css .= " mw-codereview-" . ( $row-> { $this->getDefaultSort() } <= $this->mCurSVN ? 'live' : 'notlive' ); |
280 | | - } |
281 | | - $s = "<tr class=\"$css\">\n"; |
282 | | - // Some of this stolen from Pager.php...sigh |
283 | | - $fieldNames = $this->getFieldNames(); |
284 | | - $this->mCurrentRow = $row; # In case formatValue needs to know |
285 | | - foreach ( $fieldNames as $field => $name ) { |
286 | | - $value = isset( $row->$field ) ? $row->$field : null; |
287 | | - $formatted = strval( $this->formatRevValue( $field, $value, $row ) ); |
288 | | - if ( $formatted == '' ) { |
289 | | - $formatted = ' '; |
290 | | - } |
291 | | - $class = 'TablePager_col_' . htmlspecialchars( $field ); |
292 | | - $s .= "<td class=\"$class\">$formatted</td>\n"; |
293 | | - } |
294 | | - $s .= "</tr>\n"; |
295 | | - return $s; |
296 | | - } |
297 | | - |
298 | | - function getTitle() { |
299 | | - return SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() ); |
300 | | - } |
301 | | -} |
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionAuthorLink.php |
— | — | @@ -0,0 +1,89 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +// Special:Code/MediaWiki/author/johndoe/link |
| 5 | + |
| 6 | +class CodeRevisionAuthorLink extends CodeRevisionAuthorView { |
| 7 | + function __construct( $repoName, $author ) { |
| 8 | + global $wgRequest; |
| 9 | + parent::__construct( $repoName, $author ); |
| 10 | + $this->mTarget = $wgRequest->getVal( 'linktouser' ); |
| 11 | + } |
| 12 | + |
| 13 | + function getTitle() { |
| 14 | + $repo = $this->mRepo->getName(); |
| 15 | + $auth = $this->mAuthor; |
| 16 | + return SpecialPage::getTitleFor( 'Code', "$repo/author/$auth/link" ); |
| 17 | + } |
| 18 | + |
| 19 | + function execute() { |
| 20 | + global $wgOut, $wgRequest, $wgUser; |
| 21 | + if ( !$wgUser->isAllowed( 'codereview-link-user' ) ) { |
| 22 | + $wgOut->permissionRequired( 'codereview-link-user' ); |
| 23 | + return; |
| 24 | + } |
| 25 | + if ( $wgRequest->wasPosted() ) { |
| 26 | + $this->doSubmit(); |
| 27 | + } else { |
| 28 | + $this->doForm(); |
| 29 | + } |
| 30 | + } |
| 31 | + |
| 32 | + function doForm() { |
| 33 | + global $wgOut; |
| 34 | + $form = Xml::openElement( 'form', array( 'method' => 'post', |
| 35 | + 'action' => $this->getTitle()->getLocalUrl(), |
| 36 | + 'name' => 'uluser', 'id' => 'mw-codeauthor-form1' ) ); |
| 37 | + $form .= Xml::openElement( 'fieldset' ); |
| 38 | + |
| 39 | + $additional = ''; |
| 40 | + // Is there already a user linked to this author? |
| 41 | + if ( $this->mUser ) { |
| 42 | + $form .= Xml::element( 'legend', array(), wfMsg( 'code-author-alterlink' ) ); |
| 43 | + $additional = Xml::openElement( 'fieldset' ) . |
| 44 | + Xml::element( 'legend', array(), wfMsg( 'code-author-orunlink' ) ) . |
| 45 | + Xml::submitButton( wfMsg( 'code-author-unlink' ), array( 'name' => 'unlink' ) ) . |
| 46 | + Xml::closeElement( 'fieldset' ); |
| 47 | + } else { |
| 48 | + $form .= Xml::element( 'legend', array(), wfMsg( 'code-author-dolink' ) ); |
| 49 | + } |
| 50 | + |
| 51 | + $form .= Xml::inputLabel( wfMsg( 'code-author-name' ), 'linktouser', 'username', 30, '' ) . ' ' . |
| 52 | + Xml::submitButton( wfMsg( 'ok' ), array( 'name' => 'newname' ) ) . |
| 53 | + Xml::closeElement( 'fieldset' ) . |
| 54 | + $additional . |
| 55 | + Xml::closeElement( 'form' ) . "\n"; |
| 56 | + |
| 57 | + $wgOut->addHTML( $this->linkStatus() . $form ); |
| 58 | + } |
| 59 | + |
| 60 | + function doSubmit() { |
| 61 | + global $wgOut, $wgRequest; |
| 62 | + // Link an author to a wiki user |
| 63 | + if ( strlen( $this->mTarget ) && $wgRequest->getCheck( 'newname' ) ) { |
| 64 | + $user = User::newFromName( $this->mTarget, false ); |
| 65 | + if ( !$user || !$user->getId() ) { |
| 66 | + $wgOut->addWikiMsg( 'nosuchusershort', $this->mTarget ); |
| 67 | + return; |
| 68 | + } |
| 69 | + $this->mRepo->linkUser( $this->mAuthor, $user ); |
| 70 | + $userlink = $this->mSkin->userLink( $user->getId(), $user->getName() ); |
| 71 | + $wgOut->addHTML( |
| 72 | + '<div class="successbox">' . |
| 73 | + wfMsgHtml( 'code-author-success', $this->authorLink( $this->mAuthor ), $userlink ) . |
| 74 | + '</div>' |
| 75 | + ); |
| 76 | + // Unlink an author to a wiki users |
| 77 | + } else if ( $wgRequest->getVal( 'unlink' ) ) { |
| 78 | + if ( !$this->mUser ) { |
| 79 | + $wgOut->addHTML( wfMsg( 'code-author-orphan' ) ); |
| 80 | + return; |
| 81 | + } |
| 82 | + $this->mRepo->unlinkUser( $this->mAuthor ); |
| 83 | + $wgOut->addHTML( |
| 84 | + '<div class="successbox">' . |
| 85 | + wfMsgHtml( 'code-author-unlinksuccess', $this->authorLink( $this->mAuthor ) ) . |
| 86 | + '</div>' |
| 87 | + ); |
| 88 | + } |
| 89 | + } |
| 90 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionAuthorLink.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 91 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeTagListView.php |
— | — | @@ -0,0 +1,20 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +// Special:Code/MediaWiki/tag |
| 5 | +class CodeTagListView extends CodeView { |
| 6 | + function __construct( $repoName ) { |
| 7 | + parent::__construct(); |
| 8 | + $this->mRepo = CodeRepository::newFromName( $repoName ); |
| 9 | + } |
| 10 | + |
| 11 | + function execute() { |
| 12 | + global $wgOut; |
| 13 | + $tags = $this->mRepo->getTagList(); |
| 14 | + $name = $this->mRepo->getName(); |
| 15 | + $text = ''; |
| 16 | + foreach ( $tags as $tag ) { |
| 17 | + $text .= "* [[Special:Code/$name/tag/$tag|$tag]]\n"; |
| 18 | + } |
| 19 | + $wgOut->addWikiText( $text ); |
| 20 | + } |
| 21 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeTagListView.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 22 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeStatusListView.php |
— | — | @@ -0,0 +1,20 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +// Special:Code/MediaWiki/status |
| 5 | +class CodeStatusListView extends CodeView { |
| 6 | + function __construct( $repoName ) { |
| 7 | + parent::__construct(); |
| 8 | + $this->mRepo = CodeRepository::newFromName( $repoName ); |
| 9 | + } |
| 10 | + |
| 11 | + function execute() { |
| 12 | + global $wgOut; |
| 13 | + $name = $this->mRepo->getName(); |
| 14 | + $states = CodeRevision::getPossibleStates(); |
| 15 | + $text = "== " . wfMsg ( "code-field-status" ) . " ==\n"; |
| 16 | + foreach ( $states as $state ) { |
| 17 | + $text .= "* [[Special:Code/$name/status/$state|" . wfMsg ( "code-status-" . $state ) . "]]\n"; |
| 18 | + } |
| 19 | + $wgOut->addWikiText( $text ); |
| 20 | + } |
| 21 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeStatusListView.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 22 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeCommentsListView.php |
— | — | @@ -0,0 +1,113 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +// Special:Code/MediaWiki |
| 5 | +class CodeCommentsListView extends CodeView { |
| 6 | + public $mRepo; |
| 7 | + |
| 8 | + function __construct( $repoName ) { |
| 9 | + parent::__construct(); |
| 10 | + $this->mRepo = CodeRepository::newFromName( $repoName ); |
| 11 | + } |
| 12 | + |
| 13 | + function execute() { |
| 14 | + global $wgOut; |
| 15 | + $pager = $this->getPager(); |
| 16 | + $wgOut->addHTML( |
| 17 | + $pager->getNavigationBar() . |
| 18 | + $pager->getLimitForm() . |
| 19 | + $pager->getBody() . |
| 20 | + $pager->getNavigationBar() |
| 21 | + ); |
| 22 | + } |
| 23 | + |
| 24 | + function getPager() { |
| 25 | + return new CodeCommentsTablePager( $this ); |
| 26 | + } |
| 27 | + function getRepo() { |
| 28 | + return $this->mRepo; |
| 29 | + } |
| 30 | +} |
| 31 | + |
| 32 | +// Pager for CodeRevisionListView |
| 33 | +class CodeCommentsTablePager extends TablePager { |
| 34 | + |
| 35 | + function __construct( CodeCommentsListView $view ) { |
| 36 | + global $IP; |
| 37 | + $this->mView = $view; |
| 38 | + $this->mRepo = $view->mRepo; |
| 39 | + $this->mDefaultDirection = true; |
| 40 | + $this->mCurSVN = SpecialVersion::getSvnRevision( $IP ); |
| 41 | + parent::__construct(); |
| 42 | + } |
| 43 | + |
| 44 | + function isFieldSortable( $field ) { |
| 45 | + return $field == 'cr_timestamp'; |
| 46 | + } |
| 47 | + |
| 48 | + function getDefaultSort() { return 'cc_timestamp'; } |
| 49 | + |
| 50 | + function getQueryInfo() { |
| 51 | + return array( |
| 52 | + 'tables' => array( 'code_comment', 'code_rev' ), |
| 53 | + 'fields' => array_keys( $this->getFieldNames() ), |
| 54 | + 'conds' => array( 'cc_repo_id' => $this->mRepo->getId() ), |
| 55 | + 'join_conds' => array( |
| 56 | + 'code_rev' => array( 'LEFT JOIN', 'cc_repo_id = cr_repo_id AND cc_rev_id = cr_id' ) |
| 57 | + ) |
| 58 | + ); |
| 59 | + } |
| 60 | + |
| 61 | + function getFieldNames() { |
| 62 | + return array( |
| 63 | + 'cc_timestamp' => wfMsg( 'code-field-timestamp' ), |
| 64 | + 'cc_user_text' => wfMsg( 'code-field-user' ), |
| 65 | + 'cc_rev_id' => wfMsg( 'code-field-id' ), |
| 66 | + 'cr_status' => wfMsg( 'code-field-status' ), |
| 67 | + 'cr_message' => wfMsg( 'code-field-message' ), |
| 68 | + 'cc_text' => wfMsg( 'code-field-text' ), |
| 69 | + ); |
| 70 | + } |
| 71 | + |
| 72 | + function formatValue( $name, $value ) { |
| 73 | + global $wgUser, $wgLang; |
| 74 | + switch( $name ) { |
| 75 | + case 'cc_rev_id': |
| 76 | + return $this->mView->mSkin->link( |
| 77 | + SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $value . '#code-comments' ), |
| 78 | + htmlspecialchars( $value ) ); |
| 79 | + case 'cr_status': |
| 80 | + return $this->mView->mSkin->link( |
| 81 | + SpecialPage::getTitleFor( 'Code', |
| 82 | + $this->mRepo->getName() . '/status/' . $value ), |
| 83 | + htmlspecialchars( $this->mView->statusDesc( $value ) ) ); |
| 84 | + case 'cc_user_text': |
| 85 | + return $this->mView->mSkin->userLink( -1, $value ); |
| 86 | + case 'cr_message': |
| 87 | + return $this->mView->messageFragment( $value ); |
| 88 | + case 'cc_text': |
| 89 | + # Truncate this, blah blah... |
| 90 | + return htmlspecialchars( $wgLang->truncate( $value, 300 ) ); |
| 91 | + case 'cc_timestamp': |
| 92 | + global $wgLang; |
| 93 | + return $wgLang->timeanddate( $value, true ); |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + // Note: this function is poorly factored in the parent class |
| 98 | + function formatRow( $row ) { |
| 99 | + global $wgWikiSVN; |
| 100 | + $class = "mw-codereview-status-{$row->cr_status}"; |
| 101 | + if ( $this->mRepo->mName == $wgWikiSVN ) { |
| 102 | + $class .= " mw-codereview-" . ( $row->cc_rev_id <= $this->mCurSVN ? 'live' : 'notlive' ); |
| 103 | + } |
| 104 | + return str_replace( |
| 105 | + '<tr>', |
| 106 | + Xml::openElement( 'tr', |
| 107 | + array( 'class' => $class ) ), |
| 108 | + parent::formatRow( $row ) ); |
| 109 | + } |
| 110 | + |
| 111 | + function getTitle() { |
| 112 | + return SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/comments' ); |
| 113 | + } |
| 114 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeCommentsListView.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 115 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionAuthorView.php |
— | — | @@ -0,0 +1,65 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class CodeRevisionAuthorView extends CodeRevisionListView { |
| 5 | + function __construct( $repoName, $author ) { |
| 6 | + parent::__construct( $repoName ); |
| 7 | + $this->mAuthor = $author; |
| 8 | + $this->mUser = $this->authorWikiUser( $author ); |
| 9 | + } |
| 10 | + |
| 11 | + function getPager() { |
| 12 | + return new SvnRevAuthorTablePager( $this, $this->mAuthor ); |
| 13 | + } |
| 14 | + |
| 15 | + function linkStatus() { |
| 16 | + if ( !$this->mUser ) |
| 17 | + return wfMsg( 'code-author-orphan' ); |
| 18 | + |
| 19 | + return wfMsgHtml( 'code-author-haslink', |
| 20 | + $this->mSkin->userLink( $this->mUser->getId(), $this->mUser->getName() ) . |
| 21 | + $this->mSkin->userToolLinks( $this->mUser->getId(), $this->mUser->getName() ) ); |
| 22 | + } |
| 23 | + |
| 24 | + function execute() { |
| 25 | + global $wgOut, $wgUser; |
| 26 | + |
| 27 | + $linkInfo = $this->linkStatus(); |
| 28 | + |
| 29 | + if ( $wgUser->isAllowed( 'codereview-link-user' ) ) { |
| 30 | + $repo = $this->mRepo->getName(); |
| 31 | + $page = SpecialPage::getTitleFor( 'Code', "$repo/author/$this->mAuthor/link" ); |
| 32 | + $linkInfo .= ' (' . $this->mSkin->link( $page, |
| 33 | + wfMsg( 'code-author-' . ( $this->mUser ? 'un':'' ) . 'link' ) ) . ')' ; |
| 34 | + } |
| 35 | + |
| 36 | + $repoLink = $wgUser->getSkin()->link( SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() ), |
| 37 | + htmlspecialchars( $this->mRepo->getName() ) ); |
| 38 | + $fields = array( |
| 39 | + 'code-rev-repo' => $repoLink, |
| 40 | + 'code-rev-author' => $this->authorLink( $this->mAuthor ), |
| 41 | + ); |
| 42 | + |
| 43 | + $wgOut->addHTML( $this->formatMetaData( $fields ) . $linkInfo ); |
| 44 | + |
| 45 | + parent::execute(); |
| 46 | + } |
| 47 | + |
| 48 | +} |
| 49 | + |
| 50 | +class SvnRevAuthorTablePager extends SvnRevTablePager { |
| 51 | + function __construct( $view, $author ) { |
| 52 | + parent::__construct( $view ); |
| 53 | + $this->mAuthor = $author; |
| 54 | + } |
| 55 | + |
| 56 | + function getQueryInfo() { |
| 57 | + $info = parent::getQueryInfo(); |
| 58 | + $info['conds']['cr_author'] = $this->mAuthor; // fixme: normalize input? |
| 59 | + return $info; |
| 60 | + } |
| 61 | + |
| 62 | + function getTitle() { |
| 63 | + $repo = $this->mRepo->getName(); |
| 64 | + return SpecialPage::getTitleFor( 'Code', "$repo/author/$this->mAuthor" ); |
| 65 | + } |
| 66 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionAuthorView.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 67 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeStatusChangeListView.php |
— | — | @@ -0,0 +1,113 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +// Special:Code/MediaWiki |
| 5 | +class CodeStatusChangeListView extends CodeView { |
| 6 | + public $mRepo; |
| 7 | + |
| 8 | + function __construct( $repoName ) { |
| 9 | + parent::__construct(); |
| 10 | + $this->mRepo = CodeRepository::newFromName( $repoName ); |
| 11 | + } |
| 12 | + |
| 13 | + function execute() { |
| 14 | + global $wgOut; |
| 15 | + $pager = $this->getPager(); |
| 16 | + $wgOut->addHTML( |
| 17 | + $pager->getNavigationBar() . |
| 18 | + $pager->getLimitForm() . |
| 19 | + $pager->getBody() . |
| 20 | + $pager->getNavigationBar() |
| 21 | + ); |
| 22 | + } |
| 23 | + |
| 24 | + function getPager() { |
| 25 | + return new CodeStatusChangeTablePager( $this ); |
| 26 | + } |
| 27 | + function getRepo() { |
| 28 | + return $this->mRepo; |
| 29 | + } |
| 30 | +} |
| 31 | + |
| 32 | +// Pager for CodeRevisionListView |
| 33 | +class CodeStatusChangeTablePager extends TablePager { |
| 34 | + |
| 35 | + function __construct( CodeStatusChangeListView $view ) { |
| 36 | + global $IP; |
| 37 | + $this->mView = $view; |
| 38 | + $this->mRepo = $view->mRepo; |
| 39 | + $this->mDefaultDirection = true; |
| 40 | + $this->mCurSVN = SpecialVersion::getSvnRevision( $IP ); |
| 41 | + parent::__construct(); |
| 42 | + } |
| 43 | + |
| 44 | + function isFieldSortable( $field ) { |
| 45 | + return $field == 'cpc_timestamp'; |
| 46 | + } |
| 47 | + |
| 48 | + function getDefaultSort() { return 'cpc_timestamp'; } |
| 49 | + |
| 50 | + function getQueryInfo() { |
| 51 | + return array( |
| 52 | + 'tables' => array( 'code_prop_changes', 'code_rev' ), |
| 53 | + 'fields' => array_keys( $this->getFieldNames() ), |
| 54 | + 'conds' => array( 'cpc_repo_id' => $this->mRepo->getId() ), |
| 55 | + 'join_conds' => array( |
| 56 | + 'code_rev' => array( 'LEFT JOIN', 'cpc_repo_id = cr_repo_id AND cpc_rev_id = cr_id' ) |
| 57 | + ) |
| 58 | + ); |
| 59 | + } |
| 60 | + |
| 61 | + function getFieldNames() { |
| 62 | + return array( |
| 63 | + 'cpc_timestamp' => wfMsg( 'code-field-timestamp' ), |
| 64 | + 'cpc_user_text' => wfMsg( 'code-field-user' ), |
| 65 | + 'cpc_rev_id' => wfMsg( 'code-field-id' ), |
| 66 | + 'cpc_removed' => wfMsg( 'code-field-text' ), |
| 67 | + 'cpc_removed' => wfMsg( 'code-old-status' ), |
| 68 | + 'cpc_added' => wfMsg( 'code-new-status' ), |
| 69 | + 'cr_status' => wfMsg( 'code-field-status' ), |
| 70 | + ); |
| 71 | + } |
| 72 | + |
| 73 | + function formatValue( $name, $value ) { |
| 74 | + global $wgUser, $wgLang; |
| 75 | + switch( $name ) { |
| 76 | + case 'cpc_rev_id': |
| 77 | + return $this->mView->mSkin->link( |
| 78 | + SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $value . '#code-changes' ), |
| 79 | + htmlspecialchars( $value ) ); |
| 80 | + case 'cr_status': |
| 81 | + return $this->mView->mSkin->link( |
| 82 | + SpecialPage::getTitleFor( 'Code', |
| 83 | + $this->mRepo->getName() . '/status/' . $value ), |
| 84 | + htmlspecialchars( $this->mView->statusDesc( $value ) ) ); |
| 85 | + case 'cpc_user_text': |
| 86 | + return $this->mView->mSkin->userLink( -1, $value ); |
| 87 | + case 'cpc_removed': |
| 88 | + return wfMsgHtml( $value ? "code-status-$value" : "code-status-new" ); |
| 89 | + case 'cpc_added': |
| 90 | + return wfMsgHtml( "code-status-$value" ); |
| 91 | + case 'cpc_timestamp': |
| 92 | + global $wgLang; |
| 93 | + return $wgLang->timeanddate( $value, true ); |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + // Note: this function is poorly factored in the parent class |
| 98 | + function formatRow( $row ) { |
| 99 | + global $wgWikiSVN; |
| 100 | + $class = "mw-codereview-status-{$row->cr_status}"; |
| 101 | + if ( $this->mRepo->mName == $wgWikiSVN ) { |
| 102 | + $class .= " mw-codereview-" . ( $row->cpc_rev_id <= $this->mCurSVN ? 'live' : 'notlive' ); |
| 103 | + } |
| 104 | + return str_replace( |
| 105 | + '<tr>', |
| 106 | + Xml::openElement( 'tr', |
| 107 | + array( 'class' => $class ) ), |
| 108 | + parent::formatRow( $row ) ); |
| 109 | + } |
| 110 | + |
| 111 | + function getTitle() { |
| 112 | + return SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/statuschanges' ); |
| 113 | + } |
| 114 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeStatusChangeListView.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 115 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/ui/SpecialCode.php |
— | — | @@ -0,0 +1,240 @@ |
| 2 | +<?php |
| 3 | +if ( !defined( 'MEDIAWIKI' ) ) die(); |
| 4 | + |
| 5 | +class SpecialCode extends SpecialPage { |
| 6 | + function __construct() { |
| 7 | + parent::__construct( 'Code' , 'codereview-use' ); |
| 8 | + } |
| 9 | + |
| 10 | + function execute( $subpage ) { |
| 11 | + global $wgOut, $wgRequest, $wgUser, $wgScriptPath, $wgCodeReviewStyleVersion; |
| 12 | + |
| 13 | + wfLoadExtensionMessages( 'CodeReview' ); |
| 14 | + |
| 15 | + if( !$this->userCanExecute( $wgUser ) ) { |
| 16 | + $this->displayRestrictionError(); |
| 17 | + return; |
| 18 | + } |
| 19 | + |
| 20 | + $this->setHeaders(); |
| 21 | + $wgOut->addStyle( "$wgScriptPath/extensions/CodeReview/codereview.css?$wgCodeReviewStyleVersion" ); |
| 22 | + # Remove stray slashes |
| 23 | + $subpage = preg_replace( '/\/$/', '', $subpage ); |
| 24 | + |
| 25 | + if ( $subpage == '' ) { |
| 26 | + $view = new CodeRepoListView(); |
| 27 | + } else { |
| 28 | + $params = explode( '/', $subpage ); |
| 29 | + switch( count( $params ) ) { |
| 30 | + case 1: |
| 31 | + $view = new CodeRevisionListView( $params[0] ); |
| 32 | + break; |
| 33 | + case 2: |
| 34 | + if ( $params[1] === 'tag' ) { |
| 35 | + $view = new CodeTagListView( $params[0] ); |
| 36 | + break; |
| 37 | + } elseif ( $params[1] === 'author' ) { |
| 38 | + $view = new CodeAuthorListView( $params[0] ); |
| 39 | + break; |
| 40 | + } elseif ( $params[1] === 'status' ) { |
| 41 | + $view = new CodeStatusListView( $params[0] ); |
| 42 | + break; |
| 43 | + } elseif ( $params[1] === 'comments' ) { |
| 44 | + $view = new CodeCommentsListView( $params[0] ); |
| 45 | + break; |
| 46 | + } elseif ( $params[1] === 'statuschanges' ) { |
| 47 | + $view = new CodeStatusChangeListView( $params[0] ); |
| 48 | + break; |
| 49 | + } elseif ( $params[1] === 'releasenotes' ) { |
| 50 | + $view = new CodeReleaseNotes( $params[0] ); |
| 51 | + break; |
| 52 | + } else if ( $wgRequest->wasPosted() && !$wgRequest->getCheck( 'wpPreview' ) ) { |
| 53 | + # Add any tags, Set status, Adds comments |
| 54 | + $submit = new CodeRevisionCommitter( $params[0], $params[1] ); |
| 55 | + $submit->execute(); |
| 56 | + return; |
| 57 | + } else { // revision details |
| 58 | + $view = new CodeRevisionView( $params[0], $params[1] ); |
| 59 | + break; |
| 60 | + } |
| 61 | + case 3: |
| 62 | + if ( $params[1] === 'tag' ) { |
| 63 | + $view = new CodeRevisionTagView( $params[0], $params[2] ); |
| 64 | + break; |
| 65 | + } elseif ( $params[1] === 'author' ) { |
| 66 | + $view = new CodeRevisionAuthorView( $params[0], $params[2] ); |
| 67 | + break; |
| 68 | + } elseif ( $params[1] === 'status' ) { |
| 69 | + $view = new CodeRevisionStatusView( $params[0], $params[2] ); |
| 70 | + break; |
| 71 | + } elseif ( $params[1] === 'comments' ) { |
| 72 | + $view = new CodeCommentsListView( $params[0] ); |
| 73 | + break; |
| 74 | + } else { |
| 75 | + # Nonsense parameters, back out |
| 76 | + if ( empty( $params[1] ) ) |
| 77 | + $view = new CodeRevisionListView( $params[0] ); |
| 78 | + else |
| 79 | + $view = new CodeRevisionView( $params[0], $params[1] ); |
| 80 | + break; |
| 81 | + } |
| 82 | + case 4: |
| 83 | + if ( $params[1] == 'author' && $params[3] == 'link' ) { |
| 84 | + $view = new CodeRevisionAuthorLink( $params[0], $params[2] ); |
| 85 | + break; |
| 86 | + } |
| 87 | + default: |
| 88 | + if ( $params[2] == 'reply' ) { |
| 89 | + $view = new CodeRevisionView( $params[0], $params[1], $params[3] ); |
| 90 | + break; |
| 91 | + } |
| 92 | + $wgOut->addWikiText( wfMsg( 'nosuchactiontext' ) ); |
| 93 | + $wgOut->returnToMain( null, SpecialPage::getTitleFor( 'Code' ) ); |
| 94 | + return; |
| 95 | + } |
| 96 | + } |
| 97 | + $view->execute(); |
| 98 | + |
| 99 | + // Add subtitle for easy navigation |
| 100 | + global $wgOut; |
| 101 | + if ( $view instanceof CodeView && ( $repo = $view->getRepo() ) ) { |
| 102 | + $wgOut->setSubtitle( wfMsgExt( 'codereview-subtitle', 'parse', CodeRepoListView::getNavItem( $repo->getName() ) ) ); |
| 103 | + } |
| 104 | + } |
| 105 | +} |
| 106 | + |
| 107 | +/** |
| 108 | + * Extended by CodeRevisionListView and CodeRevisionView |
| 109 | + */ |
| 110 | +abstract class CodeView { |
| 111 | + var $mRepo; |
| 112 | + |
| 113 | + function __construct() { |
| 114 | + global $wgUser; |
| 115 | + $this->mSkin = $wgUser->getSkin(); |
| 116 | + } |
| 117 | + |
| 118 | + function validPost( $permission ) { |
| 119 | + global $wgRequest, $wgUser; |
| 120 | + return $wgRequest->wasPosted() |
| 121 | + && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) |
| 122 | + && $wgUser->isAllowed( $permission ); |
| 123 | + } |
| 124 | + |
| 125 | + abstract function execute(); |
| 126 | + |
| 127 | + /* |
| 128 | + * returns a User object if $author has a wikiuser associated, |
| 129 | + * of false |
| 130 | + */ |
| 131 | + function authorWikiUser( $author ) { |
| 132 | + return $this->mRepo->authorWikiUser( $author ); |
| 133 | + } |
| 134 | + |
| 135 | + function authorLink( $author ) { |
| 136 | + $repo = $this->mRepo->getName(); |
| 137 | + $special = SpecialPage::getTitleFor( 'Code', "$repo/author/$author" ); |
| 138 | + return $this->mSkin->link( $special, htmlspecialchars( $author ) ); |
| 139 | + } |
| 140 | + |
| 141 | + function statusDesc( $status ) { |
| 142 | + return wfMsg( "code-status-$status" ); |
| 143 | + } |
| 144 | + |
| 145 | + function formatMessage( $text ) { |
| 146 | + $text = nl2br( htmlspecialchars( $text ) ); |
| 147 | + $linker = new CodeCommentLinkerHtml( $this->mRepo ); |
| 148 | + return $linker->link( $text ); |
| 149 | + } |
| 150 | + |
| 151 | + function messageFragment( $value ) { |
| 152 | + global $wgLang; |
| 153 | + $message = trim( $value ); |
| 154 | + $lines = explode( "\n", $message, 2 ); |
| 155 | + $first = $lines[0]; |
| 156 | + $trimmed = $wgLang->truncate( $first, 80 ); |
| 157 | + return $this->formatMessage( $trimmed ); |
| 158 | + } |
| 159 | + /* |
| 160 | + * Formatted HTML array for properties display |
| 161 | + * @param array fields : 'propname' => HTML data |
| 162 | + */ |
| 163 | + function formatMetaData( $fields ) { |
| 164 | + $html = '<table class="mw-codereview-meta">'; |
| 165 | + foreach ( $fields as $label => $data ) { |
| 166 | + $html .= "<tr><td>" . wfMsgHtml( $label ) . "</td><td>$data</td></tr>\n"; |
| 167 | + } |
| 168 | + return $html . "</table>\n"; |
| 169 | + } |
| 170 | + |
| 171 | + function getRepo() { |
| 172 | + if ( $this->mRepo ) |
| 173 | + return $this->mRepo; |
| 174 | + return false; |
| 175 | + } |
| 176 | +} |
| 177 | + |
| 178 | +class CodeCommentLinker { |
| 179 | + function __construct( $repo ) { |
| 180 | + global $wgUser; |
| 181 | + $this->mSkin = $wgUser->getSkin(); |
| 182 | + $this->mRepo = $repo; |
| 183 | + } |
| 184 | + |
| 185 | + function link( $text ) { |
| 186 | + # Catch links like http://www.mediawiki.org/wiki/Special:Code/MediaWiki/44245#c829 |
| 187 | + # Ended by space or brackets (like those pesky <br/> tags) |
| 188 | + $text = preg_replace_callback( '/(\b)(' . wfUrlProtocols() . ')([^ <>]+)(\b)/', array( $this, 'generalLink' ), $text ); |
| 189 | + $text = preg_replace_callback( '/\br(\d+)\b/', array( $this, 'messageRevLink' ), $text ); |
| 190 | + $text = preg_replace_callback( '/\bbug #?(\d+)\b/i', array( $this, 'messageBugLink' ), $text ); |
| 191 | + return $text; |
| 192 | + } |
| 193 | + |
| 194 | + function generalLink( $arr ) { |
| 195 | + $url = $arr[2] . $arr[3]; |
| 196 | + // Re-add the surrounding space/punctuation |
| 197 | + return $arr[1] . $this->makeExternalLink( $url, $url ) . $arr[4]; |
| 198 | + } |
| 199 | + |
| 200 | + function messageBugLink( $arr ) { |
| 201 | + $text = $arr[0]; |
| 202 | + $bugNo = intval( $arr[1] ); |
| 203 | + $url = $this->mRepo->getBugPath( $bugNo ); |
| 204 | + if ( $url ) { |
| 205 | + return $this->makeExternalLink( $url, $text ); |
| 206 | + } else { |
| 207 | + return $text; |
| 208 | + } |
| 209 | + } |
| 210 | + |
| 211 | + function messageRevLink( $matches ) { |
| 212 | + $text = $matches[0]; |
| 213 | + $rev = intval( $matches[1] ); |
| 214 | + |
| 215 | + $repo = $this->mRepo->getName(); |
| 216 | + $title = SpecialPage::getTitleFor( 'Code', "$repo/$rev" ); |
| 217 | + |
| 218 | + return $this->makeInternalLink( $title, $text ); |
| 219 | + } |
| 220 | + |
| 221 | +} |
| 222 | + |
| 223 | +class CodeCommentLinkerHtml extends CodeCommentLinker { |
| 224 | + function makeExternalLink( $url, $text ) { |
| 225 | + return $this->mSkin->makeExternalLink( $url, $text ); |
| 226 | + } |
| 227 | + |
| 228 | + function makeInternalLink( $title, $text ) { |
| 229 | + return $this->mSkin->link( $title, $text ); |
| 230 | + } |
| 231 | +} |
| 232 | + |
| 233 | +class CodeCommentLinkerWiki extends CodeCommentLinker { |
| 234 | + function makeExternalLink( $url, $text ) { |
| 235 | + return "[$url $text]"; |
| 236 | + } |
| 237 | + |
| 238 | + function makeInternalLink( $title, $text ) { |
| 239 | + return "[[" . $title->getPrefixedText() . "|$text]]"; |
| 240 | + } |
| 241 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/SpecialCode.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 242 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionCommitter.php |
— | — | @@ -0,0 +1,76 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class CodeRevisionCommitter extends CodeRevisionView { |
| 5 | + |
| 6 | + function __construct( $repoName, $rev ) { |
| 7 | + // Parent should set $this->mRepo, $this->mRev, $this->mReplyTarget |
| 8 | + parent::__construct( $repoName, $rev ); |
| 9 | + } |
| 10 | + |
| 11 | + function execute() { |
| 12 | + global $wgRequest, $wgOut, $wgUser; |
| 13 | + |
| 14 | + if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { |
| 15 | + $wgOut->addHTML( '<strong>' . wfMsg( 'sessionfailure' ) . '</strong>' ); |
| 16 | + parent::execute(); |
| 17 | + return; |
| 18 | + } |
| 19 | + if ( !$this->mRev ) { |
| 20 | + parent::execute(); |
| 21 | + return; |
| 22 | + } |
| 23 | + |
| 24 | + $redirTarget = null; |
| 25 | + $dbw = wfGetDB( DB_MASTER ); |
| 26 | + |
| 27 | + $dbw->begin(); |
| 28 | + // Change the status if allowed |
| 29 | + if ( $this->validPost( 'codereview-set-status' ) && $this->mRev->isValidStatus( $this->mStatus ) ) { |
| 30 | + $this->mRev->setStatus( $this->mStatus, $wgUser ); |
| 31 | + } |
| 32 | + $addTags = $removeTags = array(); |
| 33 | + if ( $this->validPost( 'codereview-add-tag' ) && count( $this->mAddTags ) ) { |
| 34 | + $addTags = $this->mAddTags; |
| 35 | + } |
| 36 | + if ( $this->validPost( 'codereview-remove-tag' ) && count( $this->mRemoveTags ) ) { |
| 37 | + $removeTags = $this->mRemoveTags; |
| 38 | + } |
| 39 | + // If allowed to change any tags, then do so |
| 40 | + if ( count( $addTags ) || count( $removeTags ) ) { |
| 41 | + $this->mRev->changeTags( $addTags, $removeTags, $wgUser ); |
| 42 | + } |
| 43 | + // Add any comments |
| 44 | + if ( $this->validPost( 'codereview-post-comment' ) && strlen( $this->text ) ) { |
| 45 | + $parent = $wgRequest->getIntOrNull( 'wpParent' ); |
| 46 | + $review = $wgRequest->getInt( 'wpReview' ); |
| 47 | + $isPreview = $wgRequest->getCheck( 'wpPreview' ); |
| 48 | + $id = $this->mRev->saveComment( $this->text, $review, $parent ); |
| 49 | + // For comments, take us back to the rev page focused on the new comment |
| 50 | + if ( !$this->jumpToNext ) { |
| 51 | + $redirTarget = $this->commentLink( $id ); |
| 52 | + } |
| 53 | + } |
| 54 | + $dbw->commit(); |
| 55 | + |
| 56 | + // Return to rev page |
| 57 | + if ( !$redirTitle ) { |
| 58 | + if ( $this->jumpToNext ) { |
| 59 | + if ( $next = $this->mRev->getNextUnresolved() ) { |
| 60 | + $redirTitle = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $next ); |
| 61 | + } else { |
| 62 | + $redirTitle = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() ); |
| 63 | + } |
| 64 | + } else { |
| 65 | + # $redirTarget already set for comments |
| 66 | + $redirTitle = $redirTarget ? $redirTarget : $this->revLink(); |
| 67 | + } |
| 68 | + } |
| 69 | + $wgOut->redirect( $redirTitle->getFullUrl() ); |
| 70 | + } |
| 71 | + |
| 72 | + public function validPost( $permission ) { |
| 73 | + global $wgUser, $wgRequest; |
| 74 | + return parent::validPost( $permission ) && $wgRequest->wasPosted() |
| 75 | + && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ); |
| 76 | + } |
| 77 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionCommitter.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 78 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/ui/SpecialRepoAdmin.php |
— | — | @@ -0,0 +1,123 @@ |
| 2 | +<?php |
| 3 | +if ( !defined( 'MEDIAWIKI' ) ) die(); |
| 4 | + |
| 5 | +class SpecialRepoAdmin extends SpecialPage { |
| 6 | + function __construct() { |
| 7 | + parent::__construct( 'RepoAdmin', 'repoadmin' ); |
| 8 | + } |
| 9 | + |
| 10 | + function execute( $subpage ) { |
| 11 | + global $wgRequest, $wgUser; |
| 12 | + |
| 13 | + wfLoadExtensionMessages( 'CodeReview' ); |
| 14 | + $this->setHeaders(); |
| 15 | + |
| 16 | + if ( !$this->userCanExecute( $wgUser ) ) { |
| 17 | + $this->displayRestrictionError(); |
| 18 | + return; |
| 19 | + } |
| 20 | + |
| 21 | + $repo = $wgRequest->getVal( 'repo', $subpage ); |
| 22 | + if ( $repo == '' ) { |
| 23 | + $view = new RepoAdminListView( $this ); |
| 24 | + } else { |
| 25 | + $view = new RepoAdminRepoView( $this, $repo ); |
| 26 | + } |
| 27 | + $view->execute(); |
| 28 | + } |
| 29 | +} |
| 30 | + |
| 31 | +class RepoAdminListView { |
| 32 | + var $mPage; |
| 33 | + |
| 34 | + function __construct( $page ) { |
| 35 | + $this->mPage = $page; |
| 36 | + } |
| 37 | + |
| 38 | + function getForm() { |
| 39 | + global $wgScript; |
| 40 | + return Xml::fieldset( wfMsg( 'repoadmin-new-legend' ) ) . |
| 41 | + Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . |
| 42 | + Xml::hidden( 'title', $this->mPage->getTitle()->getPrefixedDBKey() ) . |
| 43 | + Xml::inputLabel( wfMsg( 'repoadmin-new-label' ), 'repo', 'repo' ) . |
| 44 | + Xml::submitButton( wfMsg( 'repoadmin-new-button' ) ) . |
| 45 | + '</form></fieldset>'; |
| 46 | + } |
| 47 | + |
| 48 | + function execute() { |
| 49 | + global $wgOut; |
| 50 | + $wgOut->addHTML( $this->getForm() ); |
| 51 | + $repos = CodeRepository::getRepoList(); |
| 52 | + if ( !count( $repos ) ) { |
| 53 | + return; |
| 54 | + } |
| 55 | + $text = ''; |
| 56 | + foreach ( $repos as $repo ) { |
| 57 | + $name = $repo->getName(); |
| 58 | + $text .= "* [[Special:RepoAdmin/$name|$name]]\n"; |
| 59 | + } |
| 60 | + $wgOut->addWikiText( $text ); |
| 61 | + } |
| 62 | +} |
| 63 | + |
| 64 | +class RepoAdminRepoView { |
| 65 | + var $mPage; |
| 66 | + |
| 67 | + function __construct( $page, $repo ) { |
| 68 | + $this->mPage = $page; |
| 69 | + $this->mRepoName = $repo; |
| 70 | + $this->mRepo = CodeRepository::newFromName( $repo ); |
| 71 | + } |
| 72 | + |
| 73 | + function execute() { |
| 74 | + global $wgOut, $wgRequest, $wgUser; |
| 75 | + $repoExists = (bool)$this->mRepo; |
| 76 | + $repoPath = $wgRequest->getVal( 'wpRepoPath', $repoExists ? $this->mRepo->mPath : '' ); |
| 77 | + $bugPath = $wgRequest->getVal( 'wpBugPath', $repoExists ? $this->mRepo->mBugzilla : '' ); |
| 78 | + $viewPath = $wgRequest->getVal( 'wpViewPath', $repoExists ? $this->mRepo->mViewVc : '' ); |
| 79 | + if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ), $this->mRepoName ) ) { |
| 80 | + // @todo log |
| 81 | + $dbw = wfGetDB( DB_MASTER ); |
| 82 | + if ( $repoExists ) { |
| 83 | + $dbw->update( |
| 84 | + 'code_repo', |
| 85 | + array( |
| 86 | + 'repo_path' => $repoPath, |
| 87 | + 'repo_viewvc' => $viewPath, |
| 88 | + 'repo_bugzilla' => $bugPath |
| 89 | + ), |
| 90 | + array( 'repo_id' => $this->mRepo->getId() ), |
| 91 | + __METHOD__ |
| 92 | + ); |
| 93 | + } else { |
| 94 | + $dbw->insert( |
| 95 | + 'code_repo', |
| 96 | + array( |
| 97 | + 'repo_name' => $this->mRepoName, |
| 98 | + 'repo_path' => $repoPath, |
| 99 | + 'repo_viewvc' => $viewPath, |
| 100 | + 'repo_bugzilla' => $bugPath |
| 101 | + ), |
| 102 | + __METHOD__ |
| 103 | + ); |
| 104 | + } |
| 105 | + $wgOut->wrapWikiMsg( '<div class="successbox">$1</div>', array( 'repoadmin-edit-sucess', $this->mRepoName ) ); |
| 106 | + return; |
| 107 | + } |
| 108 | + $wgOut->addHTML( |
| 109 | + Xml::fieldset( wfMsg( 'repoadmin-edit-legend', $this->mRepoName ) ) . |
| 110 | + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->mPage->getTitle( $this->mRepoName )->getLocalURL() ) ) . |
| 111 | + Xml::buildForm( |
| 112 | + array( |
| 113 | + 'repoadmin-edit-path' => |
| 114 | + Xml::input( 'wpRepoPath', 60, $repoPath ), |
| 115 | + 'repoadmin-edit-bug' => |
| 116 | + Xml::input( 'wpBugPath', 60, $bugPath ), |
| 117 | + 'repoadmin-edit-view' => |
| 118 | + Xml::input( 'wpViewPath', 60, $viewPath ) ) ) . |
| 119 | + Xml::hidden( 'wpEditToken', $wgUser->editToken( $this->mRepoName ) ) . |
| 120 | + Xml::submitButton( wfMsg( 'repoadmin-edit-button' ) ) . |
| 121 | + '</form></fieldset>' |
| 122 | + ); |
| 123 | + } |
| 124 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/SpecialRepoAdmin.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 125 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionListView.php |
— | — | @@ -0,0 +1,342 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +// Special:Code/MediaWiki |
| 5 | +class CodeRevisionListView extends CodeView { |
| 6 | + public $mRepo, $mPath; |
| 7 | + function __construct( $repoName ) { |
| 8 | + global $wgRequest; |
| 9 | + parent::__construct(); |
| 10 | + $this->mRepo = CodeRepository::newFromName( $repoName ); |
| 11 | + $this->mPath = htmlspecialchars( trim( $wgRequest->getVal( 'path' ) ) ); |
| 12 | + if ( strlen( $this->mPath ) && $this->mPath[0] !== '/' ) { |
| 13 | + $this->mPath = "/{$this->mPath}"; // make sure this is a valid path |
| 14 | + } |
| 15 | + $this->mAuthor = null; |
| 16 | + } |
| 17 | + |
| 18 | + function execute() { |
| 19 | + global $wgOut, $wgUser, $wgRequest; |
| 20 | + if ( !$this->mRepo ) { |
| 21 | + $view = new CodeRepoListView(); |
| 22 | + $view->execute(); |
| 23 | + return; |
| 24 | + } |
| 25 | + |
| 26 | + // Check for batch change requests. |
| 27 | + $editToken = $wgRequest->getVal( 'wpBatchChangeEditToken' ); |
| 28 | + if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $editToken ) ) { |
| 29 | + $this->doBatchChange(); |
| 30 | + return; |
| 31 | + } |
| 32 | + |
| 33 | + $this->showForm(); |
| 34 | + $pager = $this->getPager(); |
| 35 | + |
| 36 | + // Build batch change interface as needed |
| 37 | + $this->batchForm = $wgUser->isAllowed( 'codereview-set-status' ) || |
| 38 | + $wgUser->isAllowed( 'codereview-add-tag' ); |
| 39 | + |
| 40 | + $wgOut->addHTML( |
| 41 | + $pager->getNavigationBar() . |
| 42 | + $pager->getLimitForm() . |
| 43 | + Xml::openElement( 'form', |
| 44 | + array( 'action' => $pager->getTitle()->getLocalURL(), 'method' => 'post' ) |
| 45 | + ) . |
| 46 | + $pager->getBody() . |
| 47 | + $pager->getNavigationBar() . |
| 48 | + ( $this->batchForm ? $this->buildBatchInterface( $pager ) : "" ) . |
| 49 | + Xml::closeElement( 'form' ) |
| 50 | + ); |
| 51 | + } |
| 52 | + |
| 53 | + function doBatchChange() { |
| 54 | + global $wgRequest; |
| 55 | + |
| 56 | + $revisions = $wgRequest->getArray( 'wpRevisionSelected' ); |
| 57 | + $removeTags = $wgRequest->getVal( 'wpRemoveTag' ); |
| 58 | + $addTags = $wgRequest->getVal( 'wpTag' ); |
| 59 | + $status = $wgRequest->getVal( 'wpStatus' ); |
| 60 | + |
| 61 | + // Grab data from the DB |
| 62 | + $dbr = wfGetDB( DB_SLAVE ); |
| 63 | + $revObjects = array(); |
| 64 | + $res = $dbr->select( 'code_rev', '*', array( 'cr_id' => $revisions ), __METHOD__ ); |
| 65 | + while ( $row = $dbr->fetchObject( $res ) ) { |
| 66 | + $revObjects[] = CodeRevision::newFromRow( $this->mRepo, $row ); |
| 67 | + } |
| 68 | + |
| 69 | + global $wgUser; |
| 70 | + if ( $wgUser->isAllowed( 'codereview-add-tag' ) && |
| 71 | + $addTags || $removeTags ) { |
| 72 | + $addTags = array_map( 'trim', explode( ",", $addTags ) ); |
| 73 | + $removeTags = array_map( 'trim', explode( ",", $removeTags ) ); |
| 74 | + |
| 75 | + foreach ( $revObjects as $id => $rev ) { |
| 76 | + $rev->changeTags( $addTags, $removeTags, $wgUser ); |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + if ( $wgUser->isAllowed( 'codereview-set-status' ) && |
| 81 | + $revObjects[0]->isValidStatus( $status ) ) { |
| 82 | + foreach ( $revObjects as $id => $rev ) { |
| 83 | + $rev->setStatus( $status, $wgUser ); |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + // Automatically refresh |
| 88 | + // This way of getting GET parameters is horrible, but effective. |
| 89 | + $fields = array_merge( $_GET, $_POST ); |
| 90 | + foreach ( array_keys( $fields ) as $key ) { |
| 91 | + if ( substr( $key, 0, 2 ) == 'wp' || $key == 'title' ) |
| 92 | + unset( $fields[$key] ); |
| 93 | + } |
| 94 | + |
| 95 | + global $wgOut; |
| 96 | + $wgOut->redirect( $this->getPager()->getTitle()->getFullURL( $fields ) ); |
| 97 | + } |
| 98 | + |
| 99 | + protected function buildBatchInterface( $pager ) { |
| 100 | + global $wgUser; |
| 101 | + |
| 102 | + $changeInterface = ''; |
| 103 | + $changeFields = array(); |
| 104 | + |
| 105 | + if ( $wgUser->isAllowed( 'codereview-set-status' ) ) { |
| 106 | + $changeFields['code-batch-status'] = |
| 107 | + Xml::tags( 'select', array( 'name' => 'wpStatus' ), |
| 108 | + Xml::tags( 'option', |
| 109 | + array( 'value' => '', 'selected' => 'selected' ), ' ' |
| 110 | + ) . |
| 111 | + CodeRevisionView::buildStatusList( null, $this ) |
| 112 | + ); |
| 113 | + } |
| 114 | + |
| 115 | + if ( $wgUser->isAllowed( 'codereview-add-tag' ) ) { |
| 116 | + $changeFields['code-batch-tags'] = CodeRevisionView::addTagForm( '', '' ); |
| 117 | + } |
| 118 | + |
| 119 | + if ( !count( $changeFields ) ) return ''; // nothing to do here |
| 120 | + |
| 121 | + $changeInterface = Xml::fieldset( wfMsg( 'codereview-batch-title' ), |
| 122 | + Xml::buildForm( $changeFields, 'codereview-batch-submit' ) ); |
| 123 | + |
| 124 | + $changeInterface .= $pager->getHiddenFields(); |
| 125 | + $changeInterface .= Xml::hidden( 'wpBatchChangeEditToken', $wgUser->editToken() ); |
| 126 | + |
| 127 | + return $changeInterface; |
| 128 | + } |
| 129 | + |
| 130 | + function showForm( $path = '' ) { |
| 131 | + global $wgOut, $wgScript; |
| 132 | + if ( $this->mAuthor ) { |
| 133 | + $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/author/' . $this->mAuthor ); |
| 134 | + } else { |
| 135 | + $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/path' ); |
| 136 | + } |
| 137 | + $wgOut->addHTML( |
| 138 | + Xml::openElement( 'form', array( 'action' => $wgScript, 'method' => 'get' ) ) . |
| 139 | + "<fieldset><legend>" . wfMsgHtml( 'code-pathsearch-legend' ) . "</legend>" . |
| 140 | + Xml::hidden( 'title', $special->getPrefixedDBKey() ) . |
| 141 | + Xml::inputlabel( wfMsg( "code-pathsearch-path" ), 'path', 'path', 55, $this->mPath ) . |
| 142 | + ' ' . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . |
| 143 | + "</fieldset>" . Xml::closeElement( 'form' ) |
| 144 | + ); |
| 145 | + } |
| 146 | + |
| 147 | + function getPager() { |
| 148 | + return new SvnRevTablePager( $this ); |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +// Pager for CodeRevisionListView |
| 153 | +class SvnRevTablePager extends TablePager { |
| 154 | + |
| 155 | + function __construct( $view ) { |
| 156 | + global $IP; |
| 157 | + $this->mView = $view; |
| 158 | + $this->mRepo = $view->mRepo; |
| 159 | + $this->mDefaultDirection = true; |
| 160 | + $this->mCurSVN = SpecialVersion::getSvnRevision( $IP ); |
| 161 | + parent::__construct(); |
| 162 | + } |
| 163 | + |
| 164 | + function getSVNPath() { |
| 165 | + return $this->mView->mPath; |
| 166 | + } |
| 167 | + |
| 168 | + function isFieldSortable( $field ) { |
| 169 | + return $field == $this->getDefaultSort(); |
| 170 | + } |
| 171 | + |
| 172 | + function getDefaultSort() { |
| 173 | + return strlen( $this->mView->mPath ) ? 'cp_rev_id' : 'cr_id'; |
| 174 | + } |
| 175 | + |
| 176 | + function getQueryInfo() { |
| 177 | + // Path-based query... |
| 178 | + if ( $this->getDefaultSort() === 'cp_rev_id' ) { |
| 179 | + return array( |
| 180 | + 'tables' => array( 'code_paths', 'code_rev', 'code_comment' ), |
| 181 | + 'fields' => $this->getSelectFields(), |
| 182 | + 'conds' => array( |
| 183 | + 'cp_repo_id' => $this->mRepo->getId(), |
| 184 | + 'cp_path LIKE ' . $this->mDb->addQuotes( $this->mDb->escapeLike( $this->getSVNPath() ) . '%' ), |
| 185 | + // performance |
| 186 | + 'cp_rev_id > ' . $this->mRepo->getLastStoredRev() - 20000 |
| 187 | + ), |
| 188 | + 'options' => array( 'GROUP BY' => 'cp_rev_id', 'USE INDEX' => array( 'code_path' => 'cp_repo_id' ) ), |
| 189 | + 'join_conds' => array( |
| 190 | + 'code_rev' => array( 'INNER JOIN', 'cr_repo_id = cp_repo_id AND cr_id = cp_rev_id' ), |
| 191 | + 'code_comment' => array( 'LEFT JOIN', 'cc_repo_id = cp_repo_id AND cc_rev_id = cp_rev_id' ), |
| 192 | + ) |
| 193 | + ); |
| 194 | + // No path; entire repo... |
| 195 | + } else { |
| 196 | + return array( |
| 197 | + 'tables' => array( 'code_rev', 'code_comment' ), |
| 198 | + 'fields' => $this->getSelectFields(), |
| 199 | + 'conds' => array( 'cr_repo_id' => $this->mRepo->getId() ), |
| 200 | + 'options' => array( 'GROUP BY' => 'cr_id' ), |
| 201 | + 'join_conds' => array( |
| 202 | + 'code_comment' => array( 'LEFT JOIN', 'cc_repo_id = cr_repo_id AND cc_rev_id = cr_id' ), |
| 203 | + ) |
| 204 | + ); |
| 205 | + } |
| 206 | + return false; |
| 207 | + } |
| 208 | + |
| 209 | + function getSelectFields() { |
| 210 | + return array( $this->getDefaultSort(), |
| 211 | + 'cr_id', |
| 212 | + 'cr_repo_id', |
| 213 | + 'cr_status', |
| 214 | + 'COUNT(DISTINCT cc_id) AS comments', |
| 215 | + 'cr_path', |
| 216 | + 'cr_message', |
| 217 | + 'cr_author', |
| 218 | + 'cr_timestamp' ); |
| 219 | + } |
| 220 | + |
| 221 | + function getFieldNames() { |
| 222 | + $fields = array( |
| 223 | + $this->getDefaultSort() => wfMsg( 'code-field-id' ), |
| 224 | + 'cr_status' => wfMsg( 'code-field-status' ), |
| 225 | + 'comments' => wfMsg( 'code-field-comments' ), |
| 226 | + 'tests' => wfMsg( 'code-field-tests' ), |
| 227 | + 'cr_path' => wfMsg( 'code-field-path' ), |
| 228 | + 'cr_message' => wfMsg( 'code-field-message' ), |
| 229 | + 'cr_author' => wfMsg( 'code-field-author' ), |
| 230 | + 'cr_timestamp' => wfMsg( 'code-field-timestamp' ), |
| 231 | + ); |
| 232 | + # Only show checkboxen as needed |
| 233 | + if ( !empty( $this->mView->batchForm ) ) { |
| 234 | + $fields = array( 'selectforchange' => wfMsg( 'code-field-select' ) ) + $fields; |
| 235 | + } |
| 236 | + return $fields; |
| 237 | + } |
| 238 | + |
| 239 | + function formatValue( $name, $value ) { } // unused |
| 240 | + |
| 241 | + function formatRevValue( $name, $value, $row ) { |
| 242 | + global $wgUser, $wgLang; |
| 243 | + switch( $name ) { |
| 244 | + case 'selectforchange': |
| 245 | + $sort = $this->getDefaultSort(); |
| 246 | + return Xml::check( "wpRevisionSelected[]", false, array( 'value' => $row->$sort ) ); |
| 247 | + case 'cp_rev_id': |
| 248 | + case 'cr_id': |
| 249 | + return $this->mView->mSkin->link( |
| 250 | + SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $value ), |
| 251 | + htmlspecialchars( $value ) ); |
| 252 | + case 'cr_status': |
| 253 | + return $this->mView->mSkin->link( |
| 254 | + SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/status/' . $value ), |
| 255 | + htmlspecialchars( $this->mView->statusDesc( $value ) ) |
| 256 | + ); |
| 257 | + case 'cr_author': |
| 258 | + return $this->mView->authorLink( $value ); |
| 259 | + case 'cr_message': |
| 260 | + return $this->mView->messageFragment( $value ); |
| 261 | + case 'cr_timestamp': |
| 262 | + global $wgLang; |
| 263 | + return $wgLang->timeanddate( $value, true ); |
| 264 | + case 'comments': |
| 265 | + if ( $value ) { |
| 266 | + $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $row-> { $this->getDefaultSort() } ); |
| 267 | + $special->setFragment( '#code-comments' ); |
| 268 | + return $this->mView->mSkin->link( $special, htmlspecialchars( $value ) ); |
| 269 | + } else { |
| 270 | + return intval( $value ); |
| 271 | + } |
| 272 | + case 'tests': |
| 273 | + // fixme -- this still isn't too efficient... |
| 274 | + $rev = CodeRevision::newFromRow( $this->mRepo, $row ); |
| 275 | + $runs = $rev->getTestRuns(); |
| 276 | + if( empty( $runs ) ) { |
| 277 | + return ' '; |
| 278 | + } else { |
| 279 | + $total = 0; |
| 280 | + $success = 0; |
| 281 | + $progress = false; |
| 282 | + foreach( $runs as $run ) { |
| 283 | + $total += $run->countTotal; |
| 284 | + $success += $run->countSuccess; |
| 285 | + if( $run->status == 'running' ) { |
| 286 | + $progress = true; |
| 287 | + } |
| 288 | + } |
| 289 | + if( $progress ) { |
| 290 | + global $wgStylePath; |
| 291 | + return Xml::element( 'img', array( |
| 292 | + 'src' => "$wgStylePath/common/images/spinner.gif", |
| 293 | + 'width' => 20, |
| 294 | + 'height' => 20, |
| 295 | + 'alt' => "...", |
| 296 | + 'title' => "Tests in progress...", |
| 297 | + )); |
| 298 | + } |
| 299 | + if( $success == $total ) { |
| 300 | + $class = 'mw-codereview-success'; |
| 301 | + } else { |
| 302 | + $class = 'mw-codereview-fail'; |
| 303 | + } |
| 304 | + return "<span class='$class'><strong>$success</strong>/$total</span>"; |
| 305 | + } |
| 306 | + case 'cr_path': |
| 307 | + return Xml::openElement( 'div', array( 'title' => (string)$value ) ) . |
| 308 | + $this->mView->mSkin->link( |
| 309 | + SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/path' ), |
| 310 | + $wgLang->truncate( (string)$value, 30 ), |
| 311 | + array( 'title' => (string)$value ), |
| 312 | + array( 'path' => (string)$value ) ) . "</div>"; |
| 313 | + } |
| 314 | + } |
| 315 | + |
| 316 | + // Note: this function is poorly factored in the parent class |
| 317 | + function formatRow( $row ) { |
| 318 | + global $wgWikiSVN; |
| 319 | + $css = "mw-codereview-status-{$row->cr_status}"; |
| 320 | + if ( $this->mRepo->mName == $wgWikiSVN ) { |
| 321 | + $css .= " mw-codereview-" . ( $row-> { $this->getDefaultSort() } <= $this->mCurSVN ? 'live' : 'notlive' ); |
| 322 | + } |
| 323 | + $s = "<tr class=\"$css\">\n"; |
| 324 | + // Some of this stolen from Pager.php...sigh |
| 325 | + $fieldNames = $this->getFieldNames(); |
| 326 | + $this->mCurrentRow = $row; # In case formatValue needs to know |
| 327 | + foreach ( $fieldNames as $field => $name ) { |
| 328 | + $value = isset( $row->$field ) ? $row->$field : null; |
| 329 | + $formatted = strval( $this->formatRevValue( $field, $value, $row ) ); |
| 330 | + if ( $formatted == '' ) { |
| 331 | + $formatted = ' '; |
| 332 | + } |
| 333 | + $class = 'TablePager_col_' . htmlspecialchars( $field ); |
| 334 | + $s .= "<td class=\"$class\">$formatted</td>\n"; |
| 335 | + } |
| 336 | + $s .= "</tr>\n"; |
| 337 | + return $s; |
| 338 | + } |
| 339 | + |
| 340 | + function getTitle() { |
| 341 | + return SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() ); |
| 342 | + } |
| 343 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionListView.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 344 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionTagView.php |
— | — | @@ -0,0 +1,33 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class CodeRevisionTagView extends CodeRevisionListView { |
| 5 | + function __construct( $repoName, $tag ) { |
| 6 | + parent::__construct( $repoName ); |
| 7 | + $this->mTag = $tag; |
| 8 | + } |
| 9 | + |
| 10 | + function getPager() { |
| 11 | + return new SvnRevTagTablePager( $this, $this->mTag ); |
| 12 | + } |
| 13 | +} |
| 14 | + |
| 15 | +class SvnRevTagTablePager extends SvnRevTablePager { |
| 16 | + function __construct( $view, $tag ) { |
| 17 | + parent::__construct( $view ); |
| 18 | + $this->mTag = $tag; |
| 19 | + } |
| 20 | + |
| 21 | + function getQueryInfo() { |
| 22 | + $info = parent::getQueryInfo(); |
| 23 | + $info['tables'][] = 'code_tags'; |
| 24 | + $info['conds'][] = 'cr_repo_id=ct_repo_id'; |
| 25 | + $info['conds'][] = 'cr_id=ct_rev_id'; |
| 26 | + $info['conds']['ct_tag'] = $this->mTag; // fixme: normalize input? |
| 27 | + return $info; |
| 28 | + } |
| 29 | + |
| 30 | + function getTitle() { |
| 31 | + $repo = $this->mRepo->getName(); |
| 32 | + return SpecialPage::getTitleFor( 'Code', "$repo/tag/$this->mTag" ); |
| 33 | + } |
| 34 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionTagView.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 35 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeAuthorListView.php |
— | — | @@ -0,0 +1,21 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +// Special:Code/MediaWiki/author |
| 5 | +class CodeAuthorListView extends CodeView { |
| 6 | + function __construct( $repoName ) { |
| 7 | + parent::__construct(); |
| 8 | + $this->mRepo = CodeRepository::newFromName( $repoName ); |
| 9 | + } |
| 10 | + |
| 11 | + function execute() { |
| 12 | + global $wgOut; |
| 13 | + $authors = $this->mRepo->getAuthorList(); |
| 14 | + $name = $this->mRepo->getName(); |
| 15 | + $text = wfMsg( 'code-authors-text' ) . "\n"; |
| 16 | + foreach ( $authors as $user ) { |
| 17 | + if ( $user ) |
| 18 | + $text .= "* [[Special:Code/$name/author/$user|$user]]\n"; |
| 19 | + } |
| 20 | + $wgOut->addWikiText( $text ); |
| 21 | + } |
| 22 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeAuthorListView.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 23 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeReleaseNotes.php |
— | — | @@ -0,0 +1,168 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class CodeReleaseNotes extends CodeView { |
| 5 | + function __construct( $repoName ) { |
| 6 | + global $wgRequest, $wgWikiSVN, $IP; |
| 7 | + parent::__construct( $repoName ); |
| 8 | + $this->mRepo = CodeRepository::newFromName( $repoName ); |
| 9 | + $this->mPath = htmlspecialchars( trim( $wgRequest->getVal( 'path' ) ) ); |
| 10 | + if ( strlen( $this->mPath ) && $this->mPath[0] !== '/' ) { |
| 11 | + $this->mPath = "/{$this->mPath}"; // make sure this is a valid path |
| 12 | + } |
| 13 | + $this->mPath = preg_replace( '/\/$/', '', $this->mPath ); // kill last slash |
| 14 | + $this->mStartRev = $wgRequest->getIntOrNull( 'startrev' ); |
| 15 | + $this->mEndRev = $wgRequest->getIntOrNull( 'endrev' ); |
| 16 | + # Default start rev to last live one if possible |
| 17 | + if ( !$this->mStartRev && $this->mRepo && $this->mRepo->getName() == $wgWikiSVN ) { |
| 18 | + $this->mStartRev = SpecialVersion::getSvnRevision( $IP ) + 1; |
| 19 | + } |
| 20 | + } |
| 21 | + |
| 22 | + function execute() { |
| 23 | + if ( !$this->mRepo ) { |
| 24 | + $view = new CodeRepoListView(); |
| 25 | + $view->execute(); |
| 26 | + return; |
| 27 | + } |
| 28 | + $this->showForm(); |
| 29 | + # Sanity/performance check... |
| 30 | + $lastRev = $this->mRepo->getLastStoredRev(); |
| 31 | + if ( $this->mStartRev < ( $lastRev - 3000 ) ) |
| 32 | + $this->mStartRev = NULL; |
| 33 | + # Show notes if we have at least a starting revision |
| 34 | + if ( $this->mStartRev ) { |
| 35 | + $this->showReleaseNotes(); |
| 36 | + } |
| 37 | + } |
| 38 | + |
| 39 | + protected function showForm() { |
| 40 | + global $wgOut, $wgScript, $wgUser; |
| 41 | + $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/releasenotes' ); |
| 42 | + $wgOut->addHTML( |
| 43 | + Xml::openElement( 'form', array( 'action' => $wgScript, 'method' => 'get' ) ) . |
| 44 | + "<fieldset><legend>" . wfMsgHtml( 'code-release-legend' ) . "</legend>" . |
| 45 | + Xml::hidden( 'title', $special->getPrefixedDBKey() ) . '<b>' . |
| 46 | + Xml::inputlabel( wfMsg( "code-release-startrev" ), 'startrev', 'startrev', 10, $this->mStartRev ) . |
| 47 | + '</b> ' . |
| 48 | + Xml::inputlabel( wfMsg( "code-release-endrev" ), 'endrev', 'endrev', 10, $this->mEndRev ) . |
| 49 | + ' ' . |
| 50 | + Xml::inputlabel( wfMsg( "code-pathsearch-path" ), 'path', 'path', 45, $this->mPath ) . |
| 51 | + ' ' . |
| 52 | + Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . |
| 53 | + "</fieldset>" . Xml::closeElement( 'form' ) |
| 54 | + ); |
| 55 | + } |
| 56 | + |
| 57 | + protected function showReleaseNotes() { |
| 58 | + global $wgOut; |
| 59 | + $linker = new CodeCommentLinkerHtml( $this->mRepo ); |
| 60 | + $dbr = wfGetDB( DB_SLAVE ); |
| 61 | + if ( $this->mEndRev ) { |
| 62 | + $where = 'cr_id BETWEEN ' . intval( $this->mStartRev ) . ' AND ' . intval( $this->mEndRev ); |
| 63 | + } else { |
| 64 | + $where = 'cr_id >= ' . intval( $this->mStartRev ); |
| 65 | + } |
| 66 | + if ( $this->mPath ) { |
| 67 | + $where .= ' AND (cr_path LIKE ' . $dbr->addQuotes( $dbr->escapeLike( "{$this->mPath}/" ) . '%' ); |
| 68 | + $where .= ' OR cr_path = ' . $dbr->addQuotes( $this->mPath ) . ')'; |
| 69 | + } |
| 70 | + # Select commits within this range... |
| 71 | + $res = $dbr->select( array( 'code_rev', 'code_tags' ), |
| 72 | + array( 'cr_message', 'cr_author', 'cr_id', 'ct_tag AS rnotes' ), |
| 73 | + array( |
| 74 | + 'cr_repo_id' => $this->mRepo->getId(), // this repo |
| 75 | + "cr_status NOT IN('reverted','deferred','fixme')", // not reverted/deferred/fixme |
| 76 | + "cr_message != ''", |
| 77 | + $where // in range |
| 78 | + ), |
| 79 | + __METHOD__, |
| 80 | + array( 'ORDER BY' => 'cr_id DESC' ), |
| 81 | + array( 'code_tags' => array( 'LEFT JOIN', # Tagged for release notes? |
| 82 | + 'ct_repo_id = cr_repo_id AND ct_rev_id = cr_id AND ct_tag = "release-notes"' ) |
| 83 | + ) |
| 84 | + ); |
| 85 | + $wgOut->addHTML( '<ul>' ); |
| 86 | + # Output any relevant seeming commits... |
| 87 | + while ( $row = $dbr->fetchObject( $res ) ) { |
| 88 | + $summary = htmlspecialchars( $row->cr_message ); |
| 89 | + # Add this commit summary if needed. |
| 90 | + if ( $row->rnotes || $this->isRelevant( $summary ) ) { |
| 91 | + # Keep it short if possible... |
| 92 | + $summary = $this->shortenSummary( $summary ); |
| 93 | + # Anything left? (this can happen with some heuristics) |
| 94 | + if ( $summary ) { |
| 95 | + $summary = str_replace( "\n", "<br/>", $summary ); // Newlines -> <br/> |
| 96 | + $wgOut->addHTML( "<li>" ); |
| 97 | + $wgOut->addHTML( |
| 98 | + $linker->link( $summary ) . " <i>(" . htmlspecialchars( $row->cr_author ) . |
| 99 | + ', ' . $linker->link( "r{$row->cr_id}" ) . ")</i>" |
| 100 | + ); |
| 101 | + $wgOut->addHTML( "</li>\n" ); |
| 102 | + } |
| 103 | + } |
| 104 | + } |
| 105 | + $wgOut->addHTML( '</ul>' ); |
| 106 | + } |
| 107 | + |
| 108 | + private function shortenSummary( $summary, $first = true ) { |
| 109 | + # Astericks often used for point-by-point bullets |
| 110 | + if ( preg_match( '/(^|\n) ?\*/', $summary ) ) { |
| 111 | + $blurbs = explode( '*', $summary ); |
| 112 | + # Double newlines separate importance generally |
| 113 | + } else if ( strpos( $summary, "\n\n" ) !== false ) { |
| 114 | + $blurbs = explode( "\n\n", $summary ); |
| 115 | + } else { |
| 116 | + return trim( $summary ); |
| 117 | + } |
| 118 | + $blurbs = array_map( 'trim', $blurbs ); # Clean up items |
| 119 | + $blurbs = array_filter( $blurbs ); # Filter out any garbage |
| 120 | + # Doesn't start with '*' and has some length? |
| 121 | + # If so, then assume that the top bit is important. |
| 122 | + if ( count( $blurbs ) ) { |
| 123 | + $header = strpos( ltrim( $summary ), '*' ) !== 0 && str_word_count( $blurbs[0] ) >= 5; |
| 124 | + } else { |
| 125 | + $header = false; |
| 126 | + } |
| 127 | + # Keep it short if possible... |
| 128 | + if ( count( $blurbs ) > 1 ) { |
| 129 | + $summary = array(); |
| 130 | + foreach ( $blurbs as $blurb ) { |
| 131 | + # Always show the first bit |
| 132 | + if ( $header && $first && count( $summary ) == 0 ) { |
| 133 | + $summary[] = $this->shortenSummary( $blurb, true ); |
| 134 | + # Is this bit important? Does it mention a revision? |
| 135 | + } else if ( $this->isRelevant( $blurb ) || preg_match( '/\br(\d+)\b/', $blurb ) ) { |
| 136 | + $bit = $this->shortenSummary( $blurb, false ); |
| 137 | + if ( $bit ) $summary[] = $bit; |
| 138 | + } |
| 139 | + } |
| 140 | + $summary = implode( "\n", $summary ); |
| 141 | + } else { |
| 142 | + $summary = implode( "\n", $blurbs ); |
| 143 | + } |
| 144 | + return $summary; |
| 145 | + } |
| 146 | + |
| 147 | + // Quick relevance tests (these *should* be over-inclusive a little if anything) |
| 148 | + private function isRelevant( $summary, $whole = true ) { |
| 149 | + # Fixed a bug? Mentioned a config var? |
| 150 | + if ( preg_match( '/\b(bug #?(\d+)|\$[we]g[0-9a-z]{3,50})\b/i', $summary ) ) |
| 151 | + return true; |
| 152 | + # Sanity check: summary cannot be *too* short to be useful |
| 153 | + $words = str_word_count( $summary ); |
| 154 | + if ( mb_strlen( $summary ) < 40 || $words <= 5 ) |
| 155 | + return false; |
| 156 | + # All caps words (like "BREAKING CHANGE"/magic words)? |
| 157 | + if ( preg_match( '/\b[A-Z]{6,30}\b/', $summary ) ) |
| 158 | + return true; |
| 159 | + # Random keywords |
| 160 | + if ( preg_match( '/\b(wiki|HTML\d|CSS\d|UTF-?8|(Apache|PHP|CGI|Java|Perl|Python|\w+SQL) ?\d?\.?\d?)\b/i', $summary ) ) |
| 161 | + return true; |
| 162 | + # Are we looking at the whole summary or an aspect of it? |
| 163 | + if ( $whole ) { |
| 164 | + return preg_match( '/(^|\n) ?\*/', $summary ); # List of items? |
| 165 | + } else { |
| 166 | + return true; |
| 167 | + } |
| 168 | + } |
| 169 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeReleaseNotes.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 170 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionView.php |
— | — | @@ -0,0 +1,628 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +// Special:Code/MediaWiki/40696 |
| 5 | +class CodeRevisionView extends CodeView { |
| 6 | + |
| 7 | + function __construct( $repoName, $rev, $replyTarget = null ) { |
| 8 | + global $wgRequest; |
| 9 | + parent::__construct(); |
| 10 | + $this->mRepo = CodeRepository::newFromName( $repoName ); |
| 11 | + $this->mRev = $this->mRepo ? $this->mRepo->getRevision( intval( $rev ) ) : null; |
| 12 | + $this->mPreviewText = false; |
| 13 | + # URL params... |
| 14 | + $this->mAddTags = $wgRequest->getText( 'wpTag' ); |
| 15 | + $this->mRemoveTags = $wgRequest->getText( 'wpRemoveTag' ); |
| 16 | + $this->mStatus = $wgRequest->getText( 'wpStatus' ); |
| 17 | + $this->jumpToNext = $wgRequest->getCheck( 'wpSaveAndNext' ); |
| 18 | + $this->mReplyTarget = $replyTarget ? |
| 19 | + (int)$replyTarget : $wgRequest->getIntOrNull( 'wpParent' ); |
| 20 | + $this->text = $wgRequest->getText( "wpReply{$this->mReplyTarget}" ); |
| 21 | + $this->mSkipCache = ( $wgRequest->getVal( 'action' ) == 'purge' ); |
| 22 | + # Make tag arrays |
| 23 | + $this->mAddTags = $this->splitTags( $this->mAddTags ); |
| 24 | + $this->mRemoveTags = $this->splitTags( $this->mRemoveTags ); |
| 25 | + } |
| 26 | + |
| 27 | + function execute() { |
| 28 | + global $wgOut, $wgUser, $wgLang; |
| 29 | + if ( !$this->mRepo ) { |
| 30 | + $view = new CodeRepoListView(); |
| 31 | + $view->execute(); |
| 32 | + return; |
| 33 | + } |
| 34 | + if ( !$this->mRev ) { |
| 35 | + $view = new CodeRevisionListView( $this->mRepo->getName() ); |
| 36 | + $view->execute(); |
| 37 | + return; |
| 38 | + } |
| 39 | + if( $this->mStatus == '' ) |
| 40 | + $this->mStatus = $this->mRev->getStatus(); |
| 41 | + |
| 42 | + $redirectOnPost = $this->checkPostings(); |
| 43 | + if ( $redirectOnPost ) { |
| 44 | + $wgOut->redirect( $redirectOnPost ); |
| 45 | + return; |
| 46 | + } |
| 47 | + |
| 48 | + $wgOut->setPageTitle( wfMsgHtml('code-rev-title',$this->mRev->getId()) ); |
| 49 | + |
| 50 | + $repoLink = $this->mSkin->link( SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() ), |
| 51 | + htmlspecialchars( $this->mRepo->getName() ) ); |
| 52 | + $revText = $this->navigationLinks(); |
| 53 | + $paths = ''; |
| 54 | + $modifiedPaths = $this->mRev->getModifiedPaths(); |
| 55 | + foreach ( $modifiedPaths as $row ) { |
| 56 | + $paths .= $this->formatPathLine( $row->cp_path, $row->cp_action ); |
| 57 | + } |
| 58 | + if ( $paths ) { |
| 59 | + $paths = "<div class='mw-codereview-paths'><ul>\n$paths</ul></div>\n"; |
| 60 | + } |
| 61 | + $comments = $this->formatComments(); |
| 62 | + $commentsLink = ""; |
| 63 | + if ( $comments ) { |
| 64 | + $commentsLink = " (<a href=\"#code-comments\">" . wfMsgHtml( 'code-comments' ) . "</a>)\n"; |
| 65 | + } |
| 66 | + $fields = array( |
| 67 | + 'code-rev-repo' => $repoLink, |
| 68 | + 'code-rev-rev' => $revText, |
| 69 | + 'code-rev-date' => $wgLang->timeanddate( $this->mRev->getTimestamp(), true ), |
| 70 | + 'code-rev-author' => $this->authorLink( $this->mRev->getAuthor() ), |
| 71 | + 'code-rev-status' => $this->statusForm() . $commentsLink, |
| 72 | + 'code-rev-tags' => $this->tagForm(), |
| 73 | + 'code-rev-message' => $this->formatMessage( $this->mRev->getMessage() ), |
| 74 | + 'code-rev-paths' => $paths, |
| 75 | + ); |
| 76 | + $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $this->mRev->getId() ); |
| 77 | + |
| 78 | + $html = Xml::openElement( 'form', array( 'action' => $special->getLocalUrl(), 'method' => 'post' ) ); |
| 79 | + |
| 80 | + if ( $wgUser->isAllowed( 'codereview-post-comment' ) ) { |
| 81 | + $html .= $this->addActionButtons(); |
| 82 | + } |
| 83 | + |
| 84 | + $html .= $this->formatMetaData( $fields ); |
| 85 | + # Show test case info |
| 86 | + $tests = $this->formatTests(); |
| 87 | + if( $tests ) { |
| 88 | + $html .= "<h2 id='code-tests'>" . wfMsgHtml( 'code-tests' ) . |
| 89 | + "</h2>\n" . $tests; |
| 90 | + } |
| 91 | + # Output diff |
| 92 | + if ( $this->mRev->isDiffable() ) { |
| 93 | + $diffHtml = $this->formatDiff(); |
| 94 | + $html .= |
| 95 | + "<h2>" . wfMsgHtml( 'code-rev-diff' ) . |
| 96 | + ' <small>[' . $this->mSkin->makeLinkObj( $special, |
| 97 | + wfMsg( 'code-rev-purge-link' ), 'action=purge' ) . ']</small></h2>' . |
| 98 | + "<div class='mw-codereview-diff' id='mw-codereview-diff'>" . $diffHtml . "</div>\n"; |
| 99 | + $html .= $this->formatImgDiff(); |
| 100 | + } |
| 101 | + # Show code relations |
| 102 | + $relations = $this->formatReferences(); |
| 103 | + if ( $relations ) { |
| 104 | + $html .= "<h2 id='code-references'>" . wfMsgHtml( 'code-references' ) . |
| 105 | + "</h2>\n" . $relations; |
| 106 | + } |
| 107 | + # Add revision comments |
| 108 | + if ( $comments ) { |
| 109 | + $html .= "<h2 id='code-comments'>" . wfMsgHtml( 'code-comments' ) . |
| 110 | + "</h2>\n" . $comments; |
| 111 | + } |
| 112 | + |
| 113 | + if ( $this->mReplyTarget ) { |
| 114 | + global $wgJsMimeType; |
| 115 | + $id = intval( $this->mReplyTarget ); |
| 116 | + $html .= "<script type=\"$wgJsMimeType\">addOnloadHook(function(){" . |
| 117 | + "document.getElementById('wpReplyTo$id').focus();" . |
| 118 | + "});</script>\n"; |
| 119 | + } |
| 120 | + |
| 121 | + if ( $wgUser->isAllowed( 'codereview-post-comment' ) ) { |
| 122 | + $html .= $this->addActionButtons(); |
| 123 | + } |
| 124 | + |
| 125 | + $changes = $this->formatPropChanges(); |
| 126 | + if ( $changes ) { |
| 127 | + $html .= "<h2 id='code-changes'>" . wfMsgHtml( 'code-prop-changes' ) . "</h2>\n" . $changes; |
| 128 | + } |
| 129 | + $html .= xml::closeElement( 'form' ); |
| 130 | + |
| 131 | + $wgOut->addHTML( $html ); |
| 132 | + } |
| 133 | + |
| 134 | + protected function navigationLinks() { |
| 135 | + global $wgLang; |
| 136 | + |
| 137 | + $rev = $this->mRev->getId(); |
| 138 | + $prev = $this->mRev->getPrevious(); |
| 139 | + $next = $this->mRev->getNext(); |
| 140 | + $repo = $this->mRepo->getName(); |
| 141 | + |
| 142 | + $links = array(); |
| 143 | + |
| 144 | + if ( $prev ) { |
| 145 | + $prevTarget = SpecialPage::getTitleFor( 'Code', "$repo/$prev" ); |
| 146 | + $links[] = '< ' . $this->mSkin->link( $prevTarget, "r$prev" ); |
| 147 | + } |
| 148 | + |
| 149 | + $revText = "<b>r$rev</b>"; |
| 150 | + $viewvc = $this->mRepo->getViewVcBase(); |
| 151 | + if ( $viewvc ) { |
| 152 | + $url = htmlspecialchars( "$viewvc/?view=rev&revision=$rev" ); |
| 153 | + $viewvcTxt = wfMsgHtml( 'code-rev-rev-viewvc' ); |
| 154 | + $revText .= " (<a href=\"$url\" title=\"revision $rev\">$viewvcTxt</a>)"; |
| 155 | + } |
| 156 | + $links[] = $revText; |
| 157 | + |
| 158 | + if ( $next ) { |
| 159 | + $nextTarget = SpecialPage::getTitleFor( 'Code', "$repo/$next" ); |
| 160 | + $links[] = $this->mSkin->link( $nextTarget, "r$next" ) . ' >'; |
| 161 | + } |
| 162 | + |
| 163 | + return $wgLang->pipeList( $links ); |
| 164 | + } |
| 165 | + |
| 166 | + protected function checkPostings() { |
| 167 | + global $wgRequest, $wgUser; |
| 168 | + if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { |
| 169 | + // Look for a posting... |
| 170 | + $text = $wgRequest->getText( "wpReply{$this->mReplyTarget}" ); |
| 171 | + $parent = $wgRequest->getIntOrNull( 'wpParent' ); |
| 172 | + $review = $wgRequest->getInt( 'wpReview' ); |
| 173 | + $isPreview = $wgRequest->getCheck( 'wpPreview' ); |
| 174 | + if ( $isPreview ) { |
| 175 | + // Save the text for reference on later comment display... |
| 176 | + $this->mPreviewText = $text; |
| 177 | + } |
| 178 | + } |
| 179 | + return false; |
| 180 | + } |
| 181 | + |
| 182 | + protected function formatPathLine( $path, $action ) { |
| 183 | + // Uses messages 'code-rev-modified-a', 'code-rev-modified-r', 'code-rev-modified-d', 'code-rev-modified-m' |
| 184 | + $desc = wfMsgHtml( 'code-rev-modified-' . strtolower( $action ) ); |
| 185 | + // Find any ' (from x)' from rename comment in the path. |
| 186 | + preg_match( '/ \([^\)]+\)$/', $path, $matches ); |
| 187 | + $from = isset( $matches[0] ) ? $matches[0] : ''; |
| 188 | + // Remove ' (from x)' from rename comment in the path. |
| 189 | + $path = preg_replace( '/ \([^\)]+\)$/', '', $path ); |
| 190 | + $viewvc = $this->mRepo->getViewVcBase(); |
| 191 | + $diff = ''; |
| 192 | + if ( $viewvc ) { |
| 193 | + $rev = $this->mRev->getId(); |
| 194 | + $prev = $rev - 1; |
| 195 | + $safePath = wfUrlEncode( $path ); |
| 196 | + if ( $action !== 'D' ) { |
| 197 | + $link = $this->mSkin->makeExternalLink( |
| 198 | + "$viewvc$safePath?view=markup&pathrev=$rev", |
| 199 | + $path . $from ); |
| 200 | + } else { |
| 201 | + $link = $safePath; |
| 202 | + } |
| 203 | + if ( $action !== 'A' && $action !== 'D' ) { |
| 204 | + $diff = ' (' . |
| 205 | + $this->mSkin->makeExternalLink( |
| 206 | + "$viewvc$safePath?&pathrev=$rev&r1=$prev&r2=$rev", |
| 207 | + wfMsg( 'code-rev-diff-link' ) ) . |
| 208 | + ')'; |
| 209 | + } |
| 210 | + } else { |
| 211 | + $link = $safePath; |
| 212 | + } |
| 213 | + return "<li>$link ($desc)$diff</li>\n"; |
| 214 | + } |
| 215 | + |
| 216 | + protected function tagForm() { |
| 217 | + global $wgUser; |
| 218 | + $tags = $this->mRev->getTags(); |
| 219 | + $list = ''; |
| 220 | + if ( count( $tags ) ) { |
| 221 | + $list = implode( ", ", |
| 222 | + array_map( |
| 223 | + array( $this, 'formatTag' ), |
| 224 | + $tags ) |
| 225 | + ) . ' '; |
| 226 | + } |
| 227 | + if ( $wgUser->isAllowed( 'codereview-add-tag' ) ) { |
| 228 | + $list .= $this->addTagForm( $this->mAddTags, $this->mRemoveTags ); |
| 229 | + } |
| 230 | + return $list; |
| 231 | + } |
| 232 | + |
| 233 | + protected function splitTags( $input ) { |
| 234 | + if ( !$this->mRev ) return array(); |
| 235 | + $tags = array_map( 'trim', explode( ",", $input ) ); |
| 236 | + foreach ( $tags as $key => $tag ) { |
| 237 | + $normal = $this->mRev->normalizeTag( $tag ); |
| 238 | + if ( $normal === false ) { |
| 239 | + return null; |
| 240 | + } |
| 241 | + $tags[$key] = $normal; |
| 242 | + } |
| 243 | + return $tags; |
| 244 | + } |
| 245 | + |
| 246 | + static function listTags( $tags ) { |
| 247 | + if ( empty( $tags ) ) |
| 248 | + return ""; |
| 249 | + return implode( ",", $tags ); |
| 250 | + } |
| 251 | + |
| 252 | + protected function statusForm() { |
| 253 | + global $wgUser; |
| 254 | + if ( $wgUser->isAllowed( 'codereview-set-status' ) ) { |
| 255 | + $repo = $this->mRepo->getName(); |
| 256 | + $rev = $this->mRev->getId(); |
| 257 | + return Xml::openElement( 'select', array( 'name' => 'wpStatus' ) ) . |
| 258 | + self::buildStatusList( $this->mStatus, $this ) . |
| 259 | + xml::closeElement( 'select' ); |
| 260 | + } else { |
| 261 | + return htmlspecialchars( $this->statusDesc( $this->mRev->getStatus() ) ); |
| 262 | + } |
| 263 | + } |
| 264 | + |
| 265 | + static function buildStatusList( $status, $view ) { |
| 266 | + $states = CodeRevision::getPossibleStates(); |
| 267 | + $out = ''; |
| 268 | + foreach ( $states as $state ) { |
| 269 | + $out .= Xml::option( $view->statusDesc( $state ), $state, |
| 270 | + $status === $state ); |
| 271 | + } |
| 272 | + return $out; |
| 273 | + } |
| 274 | + |
| 275 | + /** Parameters are the tags to be added/removed sent with the request */ |
| 276 | + static function addTagForm( $addTags, $removeTags ) { |
| 277 | + global $wgUser; |
| 278 | + return '<div><table><tr><td>' . |
| 279 | + Xml::inputLabel( wfMsg( 'code-rev-tag-add' ), 'wpTag', 'wpTag', 20, |
| 280 | + self::listTags( $addTags ) ) . '</td><td> </td><td>' . |
| 281 | + Xml::inputLabel( wfMsg( 'code-rev-tag-remove' ), 'wpRemoveTag', 'wpRemoveTag', 20, |
| 282 | + self::listTags( $removeTags ) ) . '</td></tr></table></div>'; |
| 283 | + } |
| 284 | + |
| 285 | + protected function formatTag( $tag ) { |
| 286 | + global $wgUser; |
| 287 | + $repo = $this->mRepo->getName(); |
| 288 | + $special = SpecialPage::getTitleFor( 'Code', "$repo/tag/$tag" ); |
| 289 | + return $this->mSkin->link( $special, htmlspecialchars( $tag ) ); |
| 290 | + } |
| 291 | + |
| 292 | + protected function formatTests() { |
| 293 | + $runs = $this->mRev->getTestRuns(); |
| 294 | + $html = ''; |
| 295 | + if( count( $runs ) ) { |
| 296 | + foreach( $runs as $run ) { |
| 297 | + $html .= "<h3>" . htmlspecialchars( $run->suite->name ) . "</h3>\n"; |
| 298 | + if( $run->status == 'complete' ) { |
| 299 | + $total = $run->countTotal; |
| 300 | + $success = $run->countSuccess; |
| 301 | + $failed = $total - $success; |
| 302 | + if( $failed ) { |
| 303 | + $html .= "<p><span class='mw-codereview-success'>$success</span> succeeded tests, " . |
| 304 | + "<span class='mw-codereview-fail'>$failed</span> failed tests:</p>"; |
| 305 | + |
| 306 | + $tests = $run->getResults( false ); |
| 307 | + $html .= "<ul>\n"; |
| 308 | + foreach( $tests as $test ) { |
| 309 | + $html .= "<li>" . htmlspecialchars( $test->caseName ) . "</li>\n"; |
| 310 | + } |
| 311 | + $html .= "</ul>\n"; |
| 312 | + } else { |
| 313 | + $html .= "<p><span class='mw-codereview-success'>$success</span> succeeded tests.</p>"; |
| 314 | + |
| 315 | + } |
| 316 | + } elseif( $run->status == "running" ) { |
| 317 | + $html .= "<p>Test cases are running...</p>"; |
| 318 | + } elseif( $run->status == "abort" ) { |
| 319 | + $html .= "<p>Test run aborted.</p>"; |
| 320 | + } else { |
| 321 | + // Err, this shouldn't happen? |
| 322 | + } |
| 323 | + } |
| 324 | + } |
| 325 | + return $html; |
| 326 | + } |
| 327 | + |
| 328 | + protected function formatDiff() { |
| 329 | + global $wgEnableAPI; |
| 330 | + |
| 331 | + // Asynchronous diff loads will require the API |
| 332 | + // And JS in the client, but tough shit eh? ;) |
| 333 | + $deferDiffs = $wgEnableAPI; |
| 334 | + |
| 335 | + if ( $this->mSkipCache ) { |
| 336 | + // We're purging the cache on purpose, probably |
| 337 | + // because the cached data was corrupt. |
| 338 | + $cache = 'skipcache'; |
| 339 | + } elseif ( $deferDiffs ) { |
| 340 | + // If data is already cached, we'll take it now; |
| 341 | + // otherwise defer the load to an AJAX request. |
| 342 | + // This lets the page be manipulable even if the |
| 343 | + // SVN connection is slow or uncooperative. |
| 344 | + $cache = 'cached'; |
| 345 | + } else { |
| 346 | + $cache = ''; |
| 347 | + } |
| 348 | + $diff = $this->mRepo->getDiff( $this->mRev->getId(), $cache ); |
| 349 | + if ( !$diff && $deferDiffs ) { |
| 350 | + // We'll try loading it by AJAX... |
| 351 | + return $this->stubDiffLoader(); |
| 352 | + } else { |
| 353 | + $hilite = new CodeDiffHighlighter(); |
| 354 | + return $hilite->render( $diff ); |
| 355 | + } |
| 356 | + } |
| 357 | + |
| 358 | + protected function formatImgDiff() { |
| 359 | + global $wgCodeReviewImgRegex; |
| 360 | + // Get image diffs |
| 361 | + $imgDiffs = $html = ''; |
| 362 | + $modifiedPaths = $this->mRev->getModifiedPaths(); |
| 363 | + foreach ( $modifiedPaths as $row ) { |
| 364 | + // Typical image file? |
| 365 | + if( preg_match($wgCodeReviewImgRegex,$row->cp_path) ) { |
| 366 | + $imgDiffs .= 'Index: '.htmlspecialchars( $row->cp_path )."\n"; |
| 367 | + $imgDiffs .= '<table border="1px" style="background:white;"><tr>'; |
| 368 | + if( $row->cp_action !== 'A' ) { // old |
| 369 | + // What was done to it? |
| 370 | + $action = $row->cp_action == 'D' ? 'code-rev-modified-d' : 'code-rev-modified-r'; |
| 371 | + // Link to old image |
| 372 | + $imgDiffs .= $this->formatImgCell( $row->cp_path, $this->mRev->getPrevious(), $action ); |
| 373 | + } |
| 374 | + if( $row->cp_action !== 'D' ) { // new |
| 375 | + // What was done to it? |
| 376 | + $action = $row->cp_action == 'A' ? 'code-rev-modified-a' : 'code-rev-modified-m'; |
| 377 | + // Link to new image |
| 378 | + $imgDiffs .= $this->formatImgCell( $row->cp_path, $this->mRev->getId(), $action ); |
| 379 | + } |
| 380 | + $imgDiffs .= "</tr></table>\n"; |
| 381 | + } |
| 382 | + } |
| 383 | + if( $imgDiffs ) { |
| 384 | + $html = '<h2>'.wfMsgHtml('code-rev-imagediff').'</h2>'; |
| 385 | + $html .= "<div class='mw-codereview-imgdiff'>$imgDiffs</div>\n"; |
| 386 | + } |
| 387 | + return $html; |
| 388 | + } |
| 389 | + |
| 390 | + protected function formatImgCell( $path, $rev, $message ) { |
| 391 | + $viewvc = $this->mRepo->getViewVcBase(); |
| 392 | + $safePath = wfUrlEncode( $path ); |
| 393 | + $url = "{$viewvc}{$safePath}?&pathrev=$rev&revision=$rev"; |
| 394 | + |
| 395 | + $alt = wfMsg( $message ); |
| 396 | + |
| 397 | + return Xml::tags( 'td', |
| 398 | + array(), |
| 399 | + Xml::tags( 'a', |
| 400 | + array( 'href' => $url ), |
| 401 | + Xml::element( 'img', |
| 402 | + array( |
| 403 | + 'src' => $url, |
| 404 | + 'alt' => $alt, |
| 405 | + 'title' => $alt, |
| 406 | + 'border' => '0' ) ) ) ); |
| 407 | + } |
| 408 | + |
| 409 | + protected function stubDiffLoader() { |
| 410 | + global $wgOut, $wgScriptPath, $wgCodeReviewStyleVersion; |
| 411 | + $encRepo = Xml::encodeJsVar( $this->mRepo->getName() ); |
| 412 | + $encRev = Xml::encodeJsVar( $this->mRev->getId() ); |
| 413 | + $wgOut->addScriptFile( "$wgScriptPath/extensions/CodeReview/codereview.js?$wgCodeReviewStyleVersion" ); |
| 414 | + $wgOut->addInlineScript( |
| 415 | + "addOnloadHook( |
| 416 | + function() { |
| 417 | + CodeReview.loadDiff($encRepo,$encRev); |
| 418 | + } |
| 419 | + );" ); |
| 420 | + return wfMsg( 'code-load-diff' ); |
| 421 | + } |
| 422 | + |
| 423 | + protected function formatComments() { |
| 424 | + $comments = implode( "\n", |
| 425 | + array_map( array( $this, 'formatCommentInline' ), $this->mRev->getComments() ) |
| 426 | + ); |
| 427 | + if ( !$this->mReplyTarget ) { |
| 428 | + $comments .= $this->postCommentForm(); |
| 429 | + } |
| 430 | + if ( !$comments ) { |
| 431 | + return false; |
| 432 | + } |
| 433 | + return "<div class='mw-codereview-comments'>$comments</div>"; |
| 434 | + } |
| 435 | + |
| 436 | + protected function formatPropChanges() { |
| 437 | + $changes = implode( "\n", |
| 438 | + array_map( array( $this, 'formatChangeInline' ), $this->mRev->getPropChanges() ) |
| 439 | + ); |
| 440 | + if ( !$changes ) { |
| 441 | + return false; |
| 442 | + } |
| 443 | + return "<ul class='mw-codereview-changes'>$changes</ul>"; |
| 444 | + } |
| 445 | + |
| 446 | + protected function formatReferences() { |
| 447 | + $refs = implode( "\n", |
| 448 | + array_map( array( $this, 'formatReferenceInline' ), $this->mRev->getReferences() ) |
| 449 | + ); |
| 450 | + if ( !$refs ) { |
| 451 | + return false; |
| 452 | + } |
| 453 | + $header = '<th>'.wfMsg( 'code-field-id' ).'</th>'; |
| 454 | + $header .= '<th>'.wfMsg( 'code-field-message' ) .'</th>'; |
| 455 | + $header .= '<th>'.wfMsg( 'code-field-author' ).'</th>'; |
| 456 | + $header .= '<th>'.wfMsg( 'code-field-timestamp' ).'</th>'; |
| 457 | + return "<table border='1' class='TablePager'><tr>{$header}</tr>{$refs}</table>"; |
| 458 | + } |
| 459 | + |
| 460 | + protected function formatCommentInline( $comment ) { |
| 461 | + if ( $comment->id === $this->mReplyTarget ) { |
| 462 | + return $this->formatComment( $comment, |
| 463 | + $this->postCommentForm( $comment->id ) ); |
| 464 | + } else { |
| 465 | + return $this->formatComment( $comment ); |
| 466 | + } |
| 467 | + } |
| 468 | + |
| 469 | + protected function formatChangeInline( $change ) { |
| 470 | + global $wgLang; |
| 471 | + $revId = $change->rev->getId(); |
| 472 | + $line = $wgLang->timeanddate( $change->timestamp, true ); |
| 473 | + $line .= ' ' . $this->mSkin->userLink( $change->user, $change->userText ); |
| 474 | + $line .= $this->mSkin->userToolLinks( $change->user, $change->userText ); |
| 475 | + // Uses messages 'code-change-status', 'code-change-tags' |
| 476 | + $line .= ' ' . wfMsgExt( "code-change-{$change->attrib}", 'parseinline', $revId ); |
| 477 | + $line .= " <i>["; |
| 478 | + // Items that were changed or set... |
| 479 | + if ( $change->removed ) { |
| 480 | + $line .= '<b>' . wfMsg( 'code-change-removed' ) . '</b> '; |
| 481 | + // Status changes... |
| 482 | + if( $change->attrib == 'status' ) { |
| 483 | + $line .= wfMsgHtml( 'code-status-'.$change->removed ); |
| 484 | + $line .= $change->added ? " " : ""; // spacing |
| 485 | + // Tag changes |
| 486 | + } else if( $change->attrib == 'tags' ) { |
| 487 | + $line .= htmlspecialchars( $change->removed ); |
| 488 | + $line .= $change->added ? " " : ""; // spacing |
| 489 | + } |
| 490 | + } |
| 491 | + // Items that were changed to something else... |
| 492 | + if ( $change->added ) { |
| 493 | + $line .= '<b>' . wfMsg( 'code-change-added' ) . '</b> '; |
| 494 | + // Status changes... |
| 495 | + if( $change->attrib == 'status' ) { |
| 496 | + $line .= wfMsgHtml( 'code-status-'.$change->added ); |
| 497 | + // Tag changes... |
| 498 | + } else { |
| 499 | + $line .= htmlspecialchars( $change->added ); |
| 500 | + } |
| 501 | + } |
| 502 | + $line .= "]</i>"; |
| 503 | + return "<li>$line</li>"; |
| 504 | + } |
| 505 | + |
| 506 | + protected function formatReferenceInline( $row ) { |
| 507 | + global $wgLang; |
| 508 | + $rev = intval( $row->cr_id ); |
| 509 | + $repo = $this->mRepo->getName(); |
| 510 | + // Borrow the code revision list css |
| 511 | + $css = 'mw-codereview-status-' . htmlspecialchars( $row->cr_status ); |
| 512 | + $date = $wgLang->timeanddate( $row->cr_timestamp, true ); |
| 513 | + $title = SpecialPage::getTitleFor( 'Code', "$repo/$rev" ); |
| 514 | + $revLink = $this->mSkin->link( $title, "r$rev" ); |
| 515 | + $summary = $this->messageFragment( $row->cr_message ); |
| 516 | + $author = $this->authorLink( $row->cr_author ); |
| 517 | + return "<tr class='$css'><td>$revLink</td><td>$summary</td><td>$author</td><td>$date</td></tr>"; |
| 518 | + } |
| 519 | + |
| 520 | + protected function commentLink( $commentId ) { |
| 521 | + $repo = $this->mRepo->getName(); |
| 522 | + $rev = $this->mRev->getId(); |
| 523 | + $title = SpecialPage::getTitleFor( 'Code', "$repo/$rev" ); |
| 524 | + $title->setFragment( "#c{$commentId}" ); |
| 525 | + return $title; |
| 526 | + } |
| 527 | + |
| 528 | + protected function revLink() { |
| 529 | + $repo = $this->mRepo->getName(); |
| 530 | + $rev = $this->mRev->getId(); |
| 531 | + $title = SpecialPage::getTitleFor( 'Code', "$repo/$rev" ); |
| 532 | + return $title; |
| 533 | + } |
| 534 | + |
| 535 | + protected function previewComment( $text, $review = 0 ) { |
| 536 | + $comment = $this->mRev->previewComment( $text, $review ); |
| 537 | + return $this->formatComment( $comment ); |
| 538 | + } |
| 539 | + |
| 540 | + protected function formatComment( $comment, $replyForm = '' ) { |
| 541 | + global $wgOut, $wgLang; |
| 542 | + $linker = new CodeCommentLinkerWiki( $this->mRepo ); |
| 543 | + |
| 544 | + if ( $comment->id === null ) { |
| 545 | + $linkId = 'cpreview'; |
| 546 | + $permaLink = "<b>Preview:</b> "; |
| 547 | + } else { |
| 548 | + $linkId = 'c' . intval( $comment->id ); |
| 549 | + $permaLink = $this->mSkin->link( $this->commentLink( $comment->id ), "#" ); |
| 550 | + } |
| 551 | + |
| 552 | + return Xml::openElement( 'div', |
| 553 | + array( |
| 554 | + 'class' => 'mw-codereview-comment', |
| 555 | + 'id' => $linkId, |
| 556 | + 'style' => $this->commentStyle( $comment ) ) ) . |
| 557 | + '<div class="mw-codereview-comment-meta">' . |
| 558 | + $permaLink . |
| 559 | + wfMsgHtml( 'code-rev-comment-by', |
| 560 | + $this->mSkin->userLink( $comment->user, $comment->userText ) . |
| 561 | + $this->mSkin->userToolLinks( $comment->user, $comment->userText ) ) . |
| 562 | + ' ' . |
| 563 | + $wgLang->timeanddate( $comment->timestamp, true ) . |
| 564 | + ' ' . |
| 565 | + $this->commentReplyLink( $comment->id ) . |
| 566 | + '</div>' . |
| 567 | + '<div class="mw-codereview-comment-text">' . |
| 568 | + $wgOut->parse( $linker->link( $comment->text ) ) . |
| 569 | + '</div>' . |
| 570 | + $replyForm . |
| 571 | + '</div>'; |
| 572 | + } |
| 573 | + |
| 574 | + protected function commentStyle( $comment ) { |
| 575 | + $depth = $comment->threadDepth(); |
| 576 | + $margin = ( $depth - 1 ) * 48; |
| 577 | + return "margin-left: ${margin}px"; |
| 578 | + } |
| 579 | + |
| 580 | + protected function commentReplyLink( $id ) { |
| 581 | + global $wgUser; |
| 582 | + if ( !$wgUser->isAllowed( 'codereview-post-comment' ) ) return ''; |
| 583 | + $repo = $this->mRepo->getName(); |
| 584 | + $rev = $this->mRev->getId(); |
| 585 | + $self = SpecialPage::getTitleFor( 'Code', "$repo/$rev/reply/$id" ); |
| 586 | + $self->setFragment( "#c$id" ); |
| 587 | + return '[' . $this->mSkin->link( $self, wfMsg( 'codereview-reply-link' ) ) . ']'; |
| 588 | + } |
| 589 | + |
| 590 | + protected function postCommentForm( $parent = null ) { |
| 591 | + global $wgUser; |
| 592 | + if ( $this->mPreviewText !== false && $parent === $this->mReplyTarget ) { |
| 593 | + $preview = $this->previewComment( $this->mPreviewText ); |
| 594 | + $text = htmlspecialchars( $this->mPreviewText ); |
| 595 | + } else { |
| 596 | + $preview = ''; |
| 597 | + $text = $this->text; |
| 598 | + } |
| 599 | + $repo = $this->mRepo->getName(); |
| 600 | + $rev = $this->mRev->getId(); |
| 601 | + if ( !$wgUser->isAllowed( 'codereview-post-comment' ) ) { |
| 602 | + return ''; |
| 603 | + } |
| 604 | + return '<div class="mw-codereview-post-comment">' . |
| 605 | + $preview . |
| 606 | + Xml::hidden( 'wpEditToken', $wgUser->editToken() ) . |
| 607 | + ( $parent ? Xml::hidden( 'wpParent', $parent ) : '' ) . |
| 608 | + '<div>' . |
| 609 | + Xml::openElement( 'textarea', array( |
| 610 | + 'name' => "wpReply{$parent}", |
| 611 | + 'id' => "wpReplyTo{$parent}", |
| 612 | + 'cols' => 40, |
| 613 | + 'rows' => 5 ) ) . |
| 614 | + $text . |
| 615 | + '</textarea>' . |
| 616 | + '</div>' . |
| 617 | + '</div>'; |
| 618 | + } |
| 619 | + |
| 620 | + protected function addActionButtons() { |
| 621 | + return '<div>' . |
| 622 | + Xml::submitButton( wfMsg( 'code-rev-submit' ), array( 'name' => 'wpSave' ) ) . |
| 623 | + ' ' . |
| 624 | + Xml::submitButton( wfMsg( 'code-rev-submit-next' ), array( 'name' => 'wpSaveAndNext' ) ) . |
| 625 | + ' ' . |
| 626 | + Xml::submitButton( wfMsg( 'code-rev-comment-preview' ), array( 'name' => 'wpPreview' ) ) . |
| 627 | + '</div>'; |
| 628 | + } |
| 629 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionView.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 630 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeRepoListView.php |
— | — | @@ -0,0 +1,34 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +// Special:Code |
| 5 | +class CodeRepoListView { |
| 6 | + |
| 7 | + function execute() { |
| 8 | + global $wgOut; |
| 9 | + $repos = CodeRepository::getRepoList(); |
| 10 | + if ( !count( $repos ) ) { |
| 11 | + $wgOut->addWikiMsg( 'code-no-repo' ); |
| 12 | + return; |
| 13 | + } |
| 14 | + $text = ''; |
| 15 | + foreach ( $repos as $repo ) { |
| 16 | + $name = $repo->getName(); |
| 17 | + $text .= "* " . self::getNavItem( $name ) . "\n"; |
| 18 | + } |
| 19 | + $wgOut->addWikiText( $text ); |
| 20 | + } |
| 21 | + |
| 22 | + public static function getNavItem( $name ) { |
| 23 | + global $wgLang; |
| 24 | + $text = "'''[[Special:Code/$name|$name]]''' ("; |
| 25 | + $links[] = "[[Special:Code/$name/comments|" . wfMsgHtml( 'code-notes' ) . "]]"; |
| 26 | + $links[] = "[[Special:Code/$name/statuschanges|" . wfMsgHtml( 'code-statuschanges' ) . "]]"; |
| 27 | + $links[] = "[[Special:Code/$name/tag|" . wfMsgHtml( 'code-tags' ) . "]]"; |
| 28 | + $links[] = "[[Special:Code/$name/author|" . wfMsgHtml( 'code-authors' ) . "]]"; |
| 29 | + $links[] = "[[Special:Code/$name/status|" . wfMsgHtml( 'code-status' ) . "]]"; |
| 30 | + $links[] = "[[Special:Code/$name/releasenotes|" . wfMsgHtml( 'code-releasenotes' ) . "]]"; |
| 31 | + $text .= $wgLang->pipeList( $links ); |
| 32 | + $text .= ")"; |
| 33 | + return $text; |
| 34 | + } |
| 35 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeRepoListView.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 36 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionStatusView.php |
— | — | @@ -0,0 +1,30 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class CodeRevisionStatusView extends CodeRevisionListView { |
| 5 | + function __construct( $repoName, $status ) { |
| 6 | + parent::__construct( $repoName ); |
| 7 | + $this->mStatus = $status; |
| 8 | + } |
| 9 | + |
| 10 | + function getPager() { |
| 11 | + return new SvnRevStatusTablePager( $this, $this->mStatus ); |
| 12 | + } |
| 13 | +} |
| 14 | + |
| 15 | +class SvnRevStatusTablePager extends SvnRevTablePager { |
| 16 | + function __construct( $view, $status ) { |
| 17 | + parent::__construct( $view ); |
| 18 | + $this->mStatus = $status; |
| 19 | + } |
| 20 | + |
| 21 | + function getQueryInfo() { |
| 22 | + $info = parent::getQueryInfo(); |
| 23 | + $info['conds']['cr_status'] = $this->mStatus; // FIXME: normalize input? |
| 24 | + return $info; |
| 25 | + } |
| 26 | + |
| 27 | + function getTitle() { |
| 28 | + $repo = $this->mRepo->getName(); |
| 29 | + return SpecialPage::getTitleFor( 'Code', "$repo/status/$this->mStatus" ); |
| 30 | + } |
| 31 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionStatusView.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 32 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/CodeReview.i18n.php |
— | — | @@ -17,14 +17,18 @@ |
18 | 18 | 'code-change-tags' => 'changed the \'\'\'tags\'\'\' for r$1', |
19 | 19 | 'code-change-removed' => 'removed:', |
20 | 20 | 'code-change-added' => 'added:', |
| 21 | + 'code-old-status' => 'Old status', |
| 22 | + 'code-new-status' => 'New status', |
21 | 23 | 'code-prop-changes' => 'Status & tagging log', |
22 | 24 | 'code-desc' => '[[Special:Code|Code review tool]] with [[Special:RepoAdmin|Subversion support]]', |
23 | 25 | 'code-no-repo' => 'No repository configured!', |
24 | 26 | 'code-load-diff' => 'Loading diff…', |
25 | 27 | 'code-notes' => 'recent comments', |
| 28 | + 'code-statuschanges' => 'status changes', |
26 | 29 | 'code-authors' => 'authors', |
27 | | - 'code-status' => 'status', |
| 30 | + 'code-status' => 'states', |
28 | 31 | 'code-tags' => 'tags', |
| 32 | + 'code-tests' => 'Test cases', |
29 | 33 | 'code-authors-text' => 'Below is a list of repo authors in order of recent commits.', |
30 | 34 | 'code-author-haslink' => 'This author is linked to the wiki user $1', |
31 | 35 | 'code-author-orphan' => 'This author has no link with a wiki account', |
— | — | @@ -43,6 +47,7 @@ |
44 | 48 | 'code-field-status' => 'Status', |
45 | 49 | 'code-field-timestamp' => 'Date', |
46 | 50 | 'code-field-comments' => 'Notes', |
| 51 | + 'code-field-tests' => 'Tests', |
47 | 52 | 'code-field-path' => 'Path', |
48 | 53 | 'code-field-text' => 'Note', |
49 | 54 | 'code-field-select' => 'Select', |
— | — | @@ -149,11 +154,10 @@ |
150 | 155 | */ |
151 | 156 | $messages['qqq'] = array( |
152 | 157 | 'code-comments' => '{{Identical|Comments}}', |
153 | | - 'code-desc' => 'Short description of the Code extension, shown on [[Special:Version]]', |
| 158 | + 'code-desc' => '{{desc}}', |
154 | 159 | 'code-field-id' => '{{Identical|Revision}}', |
155 | 160 | 'code-field-author' => '{{Identical|Author}}', |
156 | | - 'code-field-message' => 'This is probably a noun instead of verb, a column header. |
157 | | -{{Identical|Comment}}', |
| 161 | + 'code-field-message' => 'This is probably a noun instead of verb, a column header.', |
158 | 162 | 'code-field-status' => '{{Identical|Status}}', |
159 | 163 | 'code-field-timestamp' => '{{Identical|Date}}', |
160 | 164 | 'code-field-comments' => '{{Identical|Notes}}', |
— | — | @@ -587,7 +591,7 @@ |
588 | 592 | 'code-load-diff' => 'Загрузка розьніцы…', |
589 | 593 | 'code-notes' => 'апошнія камэнтары', |
590 | 594 | 'code-authors' => 'аўтары', |
591 | | - 'code-status' => 'статус', |
| 595 | + 'code-status' => 'станы', |
592 | 596 | 'code-tags' => 'тэгі', |
593 | 597 | 'code-authors-text' => 'Ніжэй пададзены сьпіс аўтараў коду ў сховішчы ў парадку апошніх зьменаў.', |
594 | 598 | 'code-author-haslink' => 'Гэты аўтар асацыяваны з удзельнікам $1', |
— | — | @@ -709,6 +713,7 @@ |
710 | 714 | 'code-field-comments' => 'Бележки', |
711 | 715 | 'code-field-path' => 'Път', |
712 | 716 | 'code-field-text' => 'Бележка', |
| 717 | + 'code-field-select' => 'Избиране', |
713 | 718 | 'code-rev-author' => 'Автор:', |
714 | 719 | 'code-rev-date' => 'Дата:', |
715 | 720 | 'code-rev-message' => 'Коментар:', |
— | — | @@ -764,14 +769,18 @@ |
765 | 770 | 'code-change-tags' => "promijenjeni ''tagovi'' za r$1", |
766 | 771 | 'code-change-removed' => 'uklonjeno:', |
767 | 772 | 'code-change-added' => 'dodano:', |
| 773 | + 'code-old-status' => 'Staro stanje', |
| 774 | + 'code-new-status' => 'Novo stanje', |
768 | 775 | 'code-prop-changes' => 'Zapis stanja i oznaka', |
769 | 776 | 'code-desc' => '[[Special:Code|Alat za provjeru koda]] sa [[Special:RepoAdmin|podrškom za subverzije]]', |
770 | 777 | 'code-no-repo' => 'Nijedan repozitorijum nije konfigurisan!', |
771 | 778 | 'code-load-diff' => 'Punim diff...', |
772 | 779 | 'code-notes' => 'nedavni komentari', |
| 780 | + 'code-statuschanges' => 'izmjene stanja', |
773 | 781 | 'code-authors' => 'autori', |
774 | | - 'code-status' => 'status', |
| 782 | + 'code-status' => 'statusi', |
775 | 783 | 'code-tags' => 'oznake', |
| 784 | + 'code-tests' => 'Probni slučajevi', |
776 | 785 | 'code-authors-text' => 'Ispod je spisak autora repozitorijuma poredanih po nedavnim doprinosima.', |
777 | 786 | 'code-author-haslink' => 'Ovaj autor je povezan wiki korisničkim računom $1', |
778 | 787 | 'code-author-orphan' => 'Ovaj autor nije povezan sa wiki računom', |
— | — | @@ -790,6 +799,7 @@ |
791 | 800 | 'code-field-status' => 'Stanje', |
792 | 801 | 'code-field-timestamp' => 'Datum', |
793 | 802 | 'code-field-comments' => 'Bilješke', |
| 803 | + 'code-field-tests' => 'Probe', |
794 | 804 | 'code-field-path' => 'Putanja', |
795 | 805 | 'code-field-text' => 'Bilješka', |
796 | 806 | 'code-field-select' => 'Odaberi', |
— | — | @@ -915,13 +925,16 @@ |
916 | 926 | 'code-change-tags' => "změnil '''značky''' revize $1", |
917 | 927 | 'code-change-removed' => 'odebráno:', |
918 | 928 | 'code-change-added' => 'přidáno:', |
| 929 | + 'code-old-status' => 'Původní stav', |
| 930 | + 'code-new-status' => 'Nový stav', |
919 | 931 | 'code-prop-changes' => 'Záznam změn stavu a značek', |
920 | 932 | 'code-desc' => '[[Special:Code|Nástroj pro kontrolu kódu]] s [[Special:RepoAdmin|podporou Subversion]]', |
921 | 933 | 'code-no-repo' => 'Nebylo nastaveno žádné úložiště!', |
922 | 934 | 'code-load-diff' => 'Nahrávám diff…', |
923 | 935 | 'code-notes' => 'nedávné poznámky', |
| 936 | + 'code-statuschanges' => 'změny stavu', |
924 | 937 | 'code-authors' => 'autoři', |
925 | | - 'code-status' => 'stav', |
| 938 | + 'code-status' => 'stavy', |
926 | 939 | 'code-tags' => 'značky', |
927 | 940 | 'code-authors-text' => 'Toto je seznam autorů v úložišti seřazený podle posledních commitů.', |
928 | 941 | 'code-author-haslink' => 'Tento autor je spojen s wiki uživatelem $1', |
— | — | @@ -1038,14 +1051,18 @@ |
1039 | 1052 | 'code-change-tags' => "änderte die '''Tags''' für Revision $1", |
1040 | 1053 | 'code-change-removed' => 'entfernt:', |
1041 | 1054 | 'code-change-added' => 'hinzugefügt:', |
| 1055 | + 'code-old-status' => 'Alter Status', |
| 1056 | + 'code-new-status' => 'Neuer Status', |
1042 | 1057 | 'code-prop-changes' => 'Status- und Tagging-Logbuch', |
1043 | 1058 | 'code-desc' => '[[Special:Code|Codeprüfungs-Werkzeug]] mit [[Special:RepoAdmin|Subversion-Unterstützung]]', |
1044 | 1059 | 'code-no-repo' => 'Kein Repositorium konfiguriert.', |
1045 | 1060 | 'code-load-diff' => 'Lade Diff …', |
1046 | 1061 | 'code-notes' => 'Jüngste Prüfnotizen', |
| 1062 | + 'code-statuschanges' => 'Änderung des Status', |
1047 | 1063 | 'code-authors' => 'Autoren', |
1048 | 1064 | 'code-status' => 'Status', |
1049 | 1065 | 'code-tags' => 'Tags', |
| 1066 | + 'code-tests' => 'Testfälle', |
1050 | 1067 | 'code-authors-text' => 'Dies ist die Liste der Autoren in der Reihefolge der Einspielungen.', |
1051 | 1068 | 'code-author-haslink' => 'Dieser Autor ist mit dem Wiki-Benutzer $1 verlinkt', |
1052 | 1069 | 'code-author-orphan' => 'Dieser Autor hat keinen Link zu einem Wiki-Benutzerkonto', |
— | — | @@ -1064,6 +1081,7 @@ |
1065 | 1082 | 'code-field-status' => 'Status', |
1066 | 1083 | 'code-field-timestamp' => 'Datum', |
1067 | 1084 | 'code-field-comments' => 'Notizen', |
| 1085 | + 'code-field-tests' => 'Tests', |
1068 | 1086 | 'code-field-path' => 'Pfad', |
1069 | 1087 | 'code-field-text' => 'Notiz', |
1070 | 1088 | 'code-field-select' => 'Auswählen', |
— | — | @@ -1159,14 +1177,18 @@ |
1160 | 1178 | 'code-change-tags' => "jo '''toflicki''' za wersiju r$1 změnił", |
1161 | 1179 | 'code-change-removed' => 'wótpórany:', |
1162 | 1180 | 'code-change-added' => 'pśidany:', |
| 1181 | + 'code-old-status' => 'Stary status', |
| 1182 | + 'code-new-status' => 'Nowy status', |
1163 | 1183 | 'code-prop-changes' => 'Protokol statusa & toflickow', |
1164 | 1184 | 'code-desc' => '[[Special:Code|Rěd za kodowu kontrolu]] z [[Special:RepoAdmin|pódpěru za Subversion]]', |
1165 | 1185 | 'code-no-repo' => 'Žeden repozitorium konfigurěrowany!', |
1166 | 1186 | 'code-load-diff' => 'Rozdźěl se zacytujo...', |
1167 | 1187 | 'code-notes' => 'aktualne komentary', |
| 1188 | + 'code-statuschanges' => 'změny statusa', |
1168 | 1189 | 'code-authors' => 'awtory', |
1169 | | - 'code-status' => 'status', |
| 1190 | + 'code-status' => 'statusy', |
1170 | 1191 | 'code-tags' => 'toflicki', |
| 1192 | + 'code-tests' => 'Testowe pady', |
1171 | 1193 | 'code-authors-text' => 'to jo lisćina awtorow repozitoriuma w rěźe aktualnych nagraśow.', |
1172 | 1194 | 'code-author-haslink' => 'Awtor jo z wikijowym wužywarjom $1 zwězany', |
1173 | 1195 | 'code-author-orphan' => 'Toś ten awtor njama wótkaz k wikijowemu kontoju', |
— | — | @@ -1185,6 +1207,7 @@ |
1186 | 1208 | 'code-field-status' => 'Status', |
1187 | 1209 | 'code-field-timestamp' => 'Datum', |
1188 | 1210 | 'code-field-comments' => 'Pśipiski', |
| 1211 | + 'code-field-tests' => 'Testy', |
1189 | 1212 | 'code-field-path' => 'Sćažka', |
1190 | 1213 | 'code-field-text' => 'Pśipisk', |
1191 | 1214 | 'code-field-select' => 'Wubraś', |
— | — | @@ -1817,6 +1840,7 @@ |
1818 | 1841 | ); |
1819 | 1842 | |
1820 | 1843 | /** Finnish (Suomi) |
| 1844 | + * @author Cimon Avaro |
1821 | 1845 | * @author Crt |
1822 | 1846 | * @author Nike |
1823 | 1847 | * @author Str4nd |
— | — | @@ -1824,19 +1848,25 @@ |
1825 | 1849 | */ |
1826 | 1850 | $messages['fi'] = array( |
1827 | 1851 | 'code' => 'Koodintarkistus', |
| 1852 | + 'code-rev-title' => 'r$1 – Koodintarkistus', |
1828 | 1853 | 'code-comments' => 'Kommentit', |
| 1854 | + 'code-references' => 'Myöhemmät versiot', |
1829 | 1855 | 'code-change-status' => "muutti version $1 '''tilaa'''", |
1830 | 1856 | 'code-change-tags' => "muutti version $1 '''merkintöjä'''", |
1831 | 1857 | 'code-change-removed' => 'poistettu:', |
1832 | 1858 | 'code-change-added' => 'lisätty:', |
| 1859 | + 'code-old-status' => 'Vanha tila', |
| 1860 | + 'code-new-status' => 'Uusi tila', |
1833 | 1861 | 'code-prop-changes' => 'Tila- ja merkintäloki', |
1834 | 1862 | 'code-desc' => '[[Special:Code|Koodintarkistustyökalu]], jossa [[Special:RepoAdmin|Subversion-tuki]].', |
1835 | 1863 | 'code-no-repo' => 'Varastoa ei ole määritetty!', |
1836 | 1864 | 'code-load-diff' => 'Ladataan eroavaisuuksia…', |
1837 | 1865 | 'code-notes' => 'tuoreet kommentit', |
| 1866 | + 'code-statuschanges' => 'tilan muutokset', |
1838 | 1867 | 'code-authors' => 'tekijät', |
1839 | | - 'code-status' => 'tila', |
| 1868 | + 'code-status' => 'tilat', |
1840 | 1869 | 'code-tags' => 'merkinnät', |
| 1870 | + 'code-tests' => 'Kokeilu tapauksia', |
1841 | 1871 | 'code-authors-text' => 'Alla on luettelo varastoon kirjoittaneista viimeisimpien lisäysten mukaisessa järjestyksessä.', |
1842 | 1872 | 'code-author-haslink' => 'Tämä tekijä on kytketty wikikäyttäjään $1', |
1843 | 1873 | 'code-author-orphan' => 'Tätä tekijää ei ole kytketty wiki-tunnukseen', |
— | — | @@ -1855,6 +1885,7 @@ |
1856 | 1886 | 'code-field-status' => 'Tila', |
1857 | 1887 | 'code-field-timestamp' => 'Päiväys', |
1858 | 1888 | 'code-field-comments' => 'Huomiot', |
| 1889 | + 'code-field-tests' => 'Testit', |
1859 | 1890 | 'code-field-path' => 'Polku', |
1860 | 1891 | 'code-field-text' => 'Huomio', |
1861 | 1892 | 'code-field-select' => 'Valitse', |
— | — | @@ -1869,6 +1900,7 @@ |
1870 | 1901 | 'code-rev-modified-r' => 'korvattu', |
1871 | 1902 | 'code-rev-modified-d' => 'poistettu', |
1872 | 1903 | 'code-rev-modified-m' => 'muutettu', |
| 1904 | + 'code-rev-imagediff' => 'Kuvamuutokset', |
1873 | 1905 | 'code-rev-status' => 'Tila:', |
1874 | 1906 | 'code-rev-status-set' => 'Vaihda tilaa', |
1875 | 1907 | 'code-rev-tags' => 'Merkinnät:', |
— | — | @@ -1891,7 +1923,15 @@ |
1892 | 1924 | 'code-pathsearch-path' => 'Polku', |
1893 | 1925 | 'code-rev-submit' => 'Tallenna muutokset', |
1894 | 1926 | 'code-rev-submit-next' => 'Tallenna ja seuraava avoin', |
| 1927 | + 'code-batch-status' => 'Muuta tilaa', |
| 1928 | + 'code-batch-tags' => 'Muuta tägejä', |
| 1929 | + 'codereview-batch-title' => 'Muuta kaikkia valittuja versioita', |
| 1930 | + 'codereview-batch-submit' => 'Lähetä', |
1895 | 1931 | 'code-releasenotes' => 'julkaisutiedot', |
| 1932 | + 'code-release-legend' => 'Luo julkaisuhuomautukset', |
| 1933 | + 'code-release-startrev' => 'Ensimmäinen versio', |
| 1934 | + 'code-release-endrev' => 'Viimeinen versio', |
| 1935 | + 'codereview-subtitle' => 'Varastolle $1', |
1896 | 1936 | 'codereview-reply-link' => 'vastaa', |
1897 | 1937 | 'codereview-email-subj' => '[$1] [r$2]: Uusi kommentti lisätty', |
1898 | 1938 | 'codereview-email-body' => 'Käyttäjä $1 jätti kommentin versioon r$3. |
— | — | @@ -1901,6 +1941,14 @@ |
1902 | 1942 | Kommentti: |
1903 | 1943 | |
1904 | 1944 | $4', |
| 1945 | + 'codereview-email-subj2' => '[$1] [r$2]: Myöhemmät muutokset', |
| 1946 | + 'codereview-email-body2' => 'Käyttäjä ”$1” teki myöhemmän muutoksen versioon r$2. |
| 1947 | + |
| 1948 | +Täydellinen URL: $3 |
| 1949 | + |
| 1950 | +Toimituksen yhteenveto: |
| 1951 | + |
| 1952 | +$4', |
1905 | 1953 | 'repoadmin' => 'Varaston hallinta', |
1906 | 1954 | 'repoadmin-new-legend' => 'Luo uusi varasto', |
1907 | 1955 | 'repoadmin-new-label' => 'Varaston nimi:', |
— | — | @@ -1912,6 +1960,7 @@ |
1913 | 1961 | 'repoadmin-edit-button' => 'OK', |
1914 | 1962 | 'repoadmin-edit-sucess' => 'Muutokset varastoon [[Special:Code/$1|$1]] on tehty.', |
1915 | 1963 | 'right-repoadmin' => 'Hallita koodivarastoja', |
| 1964 | + 'right-codereview-use' => 'Special:Code:n käyttö', |
1916 | 1965 | 'right-codereview-add-tag' => 'Lisätä uusia merkintöjä versioihin', |
1917 | 1966 | 'right-codereview-remove-tag' => 'Poistaa merkintöjä versioista', |
1918 | 1967 | 'right-codereview-post-comment' => 'Lisätä kommentteja versioihin', |
— | — | @@ -1939,13 +1988,16 @@ |
1940 | 1989 | 'code-change-tags' => "a modifié les '''balises''' de r$1", |
1941 | 1990 | 'code-change-removed' => 'retiré :', |
1942 | 1991 | 'code-change-added' => 'ajouté :', |
| 1992 | + 'code-old-status' => 'ancien statut', |
| 1993 | + 'code-new-status' => 'nouveau statut', |
1943 | 1994 | 'code-prop-changes' => 'Journal des états et du balisage', |
1944 | 1995 | 'code-desc' => '[[Special:Code|Outils de révision du code]] avec le [[Special:RepoAdmin|support de Subversion]]', |
1945 | 1996 | 'code-no-repo' => 'Pas de dépôt configuré !', |
1946 | 1997 | 'code-load-diff' => 'Chargement du diff en cours...', |
1947 | 1998 | 'code-notes' => 'commentaires récents', |
| 1999 | + 'code-statuschanges' => 'modifications de statut', |
1948 | 2000 | 'code-authors' => 'auteurs', |
1949 | | - 'code-status' => 'état', |
| 2001 | + 'code-status' => 'états', |
1950 | 2002 | 'code-tags' => 'balises', |
1951 | 2003 | 'code-authors-text' => 'Ci-dessous se trouve une liste des auteurs de dépôts dans l’ordre des publications récentes.', |
1952 | 2004 | 'code-author-haslink' => 'Cet auteur est lié au compte $1 de ce wiki', |
— | — | @@ -2076,14 +2128,18 @@ |
2077 | 2129 | 'code-change-tags' => "cambiou as '''etiquetas''' da r$1", |
2078 | 2130 | 'code-change-removed' => 'eliminado:', |
2079 | 2131 | 'code-change-added' => 'engadido:', |
| 2132 | + 'code-old-status' => 'Estado vello', |
| 2133 | + 'code-new-status' => 'Novo estado', |
2080 | 2134 | 'code-prop-changes' => 'Estado e rexistro de etiquetas', |
2081 | 2135 | 'code-desc' => '[[Special:Code|Ferramenta de revisión do código]] con [[Special:RepoAdmin|apoio da subversión]]', |
2082 | 2136 | 'code-no-repo' => 'Non hai ningún repositorio configurado!', |
2083 | 2137 | 'code-load-diff' => 'Cargando as diferenzas…', |
2084 | 2138 | 'code-notes' => 'comentarios recentes', |
| 2139 | + 'code-statuschanges' => 'cambios de estado', |
2085 | 2140 | 'code-authors' => 'autores', |
2086 | | - 'code-status' => 'estado', |
| 2141 | + 'code-status' => 'estados', |
2087 | 2142 | 'code-tags' => 'etiquetas', |
| 2143 | + 'code-tests' => 'Casos de proba', |
2088 | 2144 | 'code-authors-text' => 'Embaixo hai unha lista dos autores das respostas por orde de tarefas recentes.', |
2089 | 2145 | 'code-author-haslink' => 'O autor é ligado co usuario do wiki chamado $1', |
2090 | 2146 | 'code-author-orphan' => 'Este autor non ten ningunha ligazón con algunha conta do wiki', |
— | — | @@ -2102,6 +2158,7 @@ |
2103 | 2159 | 'code-field-status' => 'Estado', |
2104 | 2160 | 'code-field-timestamp' => 'Data', |
2105 | 2161 | 'code-field-comments' => 'Notas', |
| 2162 | + 'code-field-tests' => 'Probas', |
2106 | 2163 | 'code-field-path' => 'Ruta', |
2107 | 2164 | 'code-field-text' => 'Nota', |
2108 | 2165 | 'code-field-select' => 'Seleccionar', |
— | — | @@ -2205,13 +2262,13 @@ |
2206 | 2263 | 'code-status' => 'κατάστασις', |
2207 | 2264 | 'code-tags' => 'προσαρτήματα', |
2208 | 2265 | 'code-authors-text' => 'Κάτωθι ἐστὶ καταλογή τις δημιουργῶν τῆς ἀποθήκης κατατάξει τῶν πλείω προσφάτων καταθέσεων.', |
2209 | | - 'code-author-haslink' => 'Ὅδε ὁ δημιουργὸς συνδεδεμένος ἐστὶ μετὰ τοῦ ϝίκι-χρωμένου $1', |
2210 | | - 'code-author-orphan' => 'Ὅδε ὁ χρώμενος οὐκ ἔχει σύνδεσμον μετὰ ϝίκι-λογισμοῦ τινός', |
2211 | | - 'code-author-dolink' => 'Συνδεῖσθαι τόνδε τὸν δημιουργὸν μετὰ ϝικι-χρωμένου τινὸς:', |
2212 | | - 'code-author-alterlink' => 'Ἀλλάττειν ϝίκι-χρώμενον τὸν συνδεδεμένον μετὰ τοῦδε τοῦ χρωμένου:', |
| 2266 | + 'code-author-haslink' => 'Ὅδε ὁ δημιουργὸς συνδεδεμένος ἐστὶ μετὰ τοῦ βικι-χρωμένου $1', |
| 2267 | + 'code-author-orphan' => 'Ὅδε ὁ χρώμενος οὐκ ἔχει σύνδεσμον μετὰ βικι-λογισμοῦ τινός', |
| 2268 | + 'code-author-dolink' => 'Συνδεῖσθαι τόνδε τὸν δημιουργὸν μετὰ βικι-χρωμένου τινός:', |
| 2269 | + 'code-author-alterlink' => 'Ἀλλάττειν βικι-χρώμενον τὸν συνδεδεμένον μετὰ τοῦδε τοῦ χρωμένου:', |
2213 | 2270 | 'code-author-orunlink' => 'Ἢ ἀποδιασυνδεῖσθαι τόνδε τὸν χρώμενον:', |
2214 | 2271 | 'code-author-name' => 'Εὶσάγειν ὄνομα χρωμένου τι:', |
2215 | | - 'code-author-success' => 'Ὁ δημιουργὸς $1 διασυνδεδεμένος ἐστὶ μετὰ τοῦ ϝικι-χρωμένου $2', |
| 2272 | + 'code-author-success' => 'Ὁ δημιουργὸς $1 διασυνδεδεμένος ἐστὶ μετὰ τοῦ βικι-χρωμένου $2', |
2216 | 2273 | 'code-author-link' => 'συνδεῖσθαι;', |
2217 | 2274 | 'code-author-unlink' => 'ἀσυνδεῖσθαι;', |
2218 | 2275 | 'code-author-unlinksuccess' => 'Ὁ δημιουργὸς $1 ἀποδιασυνδεδεμένος ἐστίν', |
— | — | @@ -2224,6 +2281,7 @@ |
2225 | 2282 | 'code-field-comments' => 'Σημειώματα', |
2226 | 2283 | 'code-field-path' => 'Ἀτραπός', |
2227 | 2284 | 'code-field-text' => 'Σημείωμα', |
| 2285 | + 'code-field-select' => 'Ἐπιλέγειν', |
2228 | 2286 | 'code-rev-author' => 'Δημιουργός:', |
2229 | 2287 | 'code-rev-date' => 'Ἡμερομηνία:', |
2230 | 2288 | 'code-rev-message' => 'Σχόλιον:', |
— | — | @@ -2282,7 +2340,7 @@ |
2283 | 2341 | 'right-codereview-remove-tag' => 'Ἀφαιρεῖν προσαρτήματα ὑπὸ τὰς ἀναθεωρήσεις', |
2284 | 2342 | 'right-codereview-post-comment' => 'Προστιθέναι νέα σχόλια ταῖς ἀναθεωρήσεσιν', |
2285 | 2343 | 'right-codereview-set-status' => 'Μεταβάλλειν τὸ καθεστὼς τῶν ἀναθεωρήσεων', |
2286 | | - 'right-codereview-link-user' => 'Συνδεῖσθαι τοὺς δημιουργοὺς μετὰ ϝικι-χρωμένων', |
| 2344 | + 'right-codereview-link-user' => 'Συνδεῖσθαι τοὺς δημιουργοὺς μετὰ βικι-χρωμένων', |
2287 | 2345 | 'specialpages-group-developer' => 'Ἐργαλεῖα ἀναπτυκτῶν', |
2288 | 2346 | ); |
2289 | 2347 | |
— | — | @@ -2298,11 +2356,14 @@ |
2299 | 2357 | 'code-change-tags' => "het d '''Tag''' vu r$1 gänderet", |
2300 | 2358 | 'code-change-removed' => 'usegnuh:', |
2301 | 2359 | 'code-change-added' => 'zuegfiegt:', |
| 2360 | + 'code-old-status' => 'Alte Status', |
| 2361 | + 'code-new-status' => 'Neje Status', |
2302 | 2362 | 'code-prop-changes' => 'Status- un Tagging-Logbuech', |
2303 | 2363 | 'code-desc' => '[[Special:Code|Codepriefigs-Wärchzyyg]] mit [[Special:RepoAdmin|Subversion-Unterstitzig]]', |
2304 | 2364 | 'code-no-repo' => 'Kei Repositorium konfiguriert.', |
2305 | 2365 | 'code-load-diff' => 'Diff am Lade…', |
2306 | 2366 | 'code-notes' => 'Priefnotize', |
| 2367 | + 'code-statuschanges' => 'Statusänderige', |
2307 | 2368 | 'code-authors' => 'Autore', |
2308 | 2369 | 'code-status' => 'Status', |
2309 | 2370 | 'code-tags' => 'Tag', |
— | — | @@ -2427,13 +2488,16 @@ |
2428 | 2489 | 'code-change-tags' => "שינה את ה'''תגיות''' של גרסה $1", |
2429 | 2490 | 'code-change-removed' => 'הוסרו:', |
2430 | 2491 | 'code-change-added' => 'נוספו:', |
| 2492 | + 'code-old-status' => 'המצב הישן', |
| 2493 | + 'code-new-status' => 'המצב החדש', |
2431 | 2494 | 'code-prop-changes' => 'יומן מצב ותגיות', |
2432 | 2495 | 'code-desc' => '[[Special:Code|כלי בדיקת קוד]] עם [[Special:RepoAdmin|תמיכה ב־Subversion]]', |
2433 | 2496 | 'code-no-repo' => 'לא הוגדר מאגר!', |
2434 | 2497 | 'code-load-diff' => 'ההבדל בין הגרסאות בטעינה…', |
2435 | 2498 | 'code-notes' => 'הערות אחרונות', |
| 2499 | + 'code-statuschanges' => 'שינויי מצב', |
2436 | 2500 | 'code-authors' => 'כותבים', |
2437 | | - 'code-status' => 'מצב', |
| 2501 | + 'code-status' => 'מצבים', |
2438 | 2502 | 'code-tags' => 'תגיות', |
2439 | 2503 | 'code-authors-text' => 'להלן רשימת הכותבים במאגר לפי סדר השינויים האחרונים בקוד.', |
2440 | 2504 | 'code-author-haslink' => 'כותב זה מקושר למשתמש הוויקי $1', |
— | — | @@ -2668,14 +2732,18 @@ |
2669 | 2733 | 'code-change-tags' => "změni '''taflički''' za wersiju r$1", |
2670 | 2734 | 'code-change-removed' => 'wotstronjeny:', |
2671 | 2735 | 'code-change-added' => 'přidaty:', |
| 2736 | + 'code-old-status' => 'Stary status', |
| 2737 | + 'code-new-status' => 'Nowy status', |
2672 | 2738 | 'code-prop-changes' => 'Protokol wo statusu & tafličkach', |
2673 | 2739 | 'code-desc' => '[[Special:Code|Nastroj za kodowu kontrolu]] z [[Special:RepoAdmin|podpěru za Subversion]]', |
2674 | 2740 | 'code-no-repo' => 'Žadyn repozitorij konfigurowany', |
2675 | 2741 | 'code-load-diff' => 'Rozdźěl so začituje...', |
2676 | 2742 | 'code-notes' => 'aktualne komentary', |
| 2743 | + 'code-statuschanges' => 'změny statusa', |
2677 | 2744 | 'code-authors' => 'awtorojo', |
2678 | | - 'code-status' => 'status', |
| 2745 | + 'code-status' => 'statusy', |
2679 | 2746 | 'code-tags' => 'taflički', |
| 2747 | + 'code-tests' => 'Testowe pady', |
2680 | 2748 | 'code-authors-text' => 'To je lisćina awtorojo repozitorija po porjedźe aktualnych nahraćow.', |
2681 | 2749 | 'code-author-haslink' => 'Tutón awtor ma wotkaz na wikijoweho wužiwarja $1', |
2682 | 2750 | 'code-author-orphan' => 'Tutón awtor nima wotkaz k wikijowemu kontu', |
— | — | @@ -2694,6 +2762,7 @@ |
2695 | 2763 | 'code-field-status' => 'Status', |
2696 | 2764 | 'code-field-timestamp' => 'Datum', |
2697 | 2765 | 'code-field-comments' => 'Přispomnjenki', |
| 2766 | + 'code-field-tests' => 'Testy', |
2698 | 2767 | 'code-field-path' => 'Šćežka', |
2699 | 2768 | 'code-field-text' => 'Přispomnjenka', |
2700 | 2769 | 'code-field-select' => 'Wubrać', |
— | — | @@ -2882,13 +2951,16 @@ |
2883 | 2952 | 'code-change-tags' => "cambiava le '''etiquettas''' de v$1", |
2884 | 2953 | 'code-change-removed' => 'removeva:', |
2885 | 2954 | 'code-change-added' => 'addeva:', |
| 2955 | + 'code-old-status' => 'Stato ancian', |
| 2956 | + 'code-new-status' => 'Stato nove', |
2886 | 2957 | 'code-prop-changes' => 'Registro de stato e de etiquettage', |
2887 | 2958 | 'code-desc' => '[[Special:Code|Instrumento pro revider le codice]] con [[Special:RepoAdmin|supporto de Subversion]]', |
2888 | 2959 | 'code-no-repo' => 'Nulle deposito configurate!', |
2889 | 2960 | 'code-load-diff' => 'Cargamento del diff in curso…', |
2890 | 2961 | 'code-notes' => 'commentos recente', |
| 2962 | + 'code-statuschanges' => 'cambios de stato', |
2891 | 2963 | 'code-authors' => 'autores', |
2892 | | - 'code-status' => 'stato', |
| 2964 | + 'code-status' => 'statos', |
2893 | 2965 | 'code-tags' => 'etiquettas', |
2894 | 2966 | 'code-authors-text' => 'Infra es un lista de autores del deposito in ordine de publicationes recente.', |
2895 | 2967 | 'code-author-haslink' => 'Iste autor es ligate al usator $1 de iste wiki', |
— | — | @@ -2991,6 +3063,34 @@ |
2992 | 3064 | 'specialpages-group-developer' => 'Instrumentos pro disveloppatores', |
2993 | 3065 | ); |
2994 | 3066 | |
| 3067 | +/** Indonesian (Bahasa Indonesia) |
| 3068 | + * @author Bennylin |
| 3069 | + */ |
| 3070 | +$messages['id'] = array( |
| 3071 | + 'code-comments' => 'Komentar', |
| 3072 | + 'code-status' => 'status', |
| 3073 | + 'code-field-id' => 'Revisi', |
| 3074 | + 'code-field-author' => 'Pembuat', |
| 3075 | + 'code-field-status' => 'Status', |
| 3076 | + 'code-field-timestamp' => 'Tanggal', |
| 3077 | + 'code-field-comments' => 'Catatan', |
| 3078 | + 'code-field-path' => 'Jalan', |
| 3079 | + 'code-rev-author' => 'Pembuat:', |
| 3080 | + 'code-rev-date' => 'Tanggal:', |
| 3081 | + 'code-rev-message' => 'Komentar:', |
| 3082 | + 'code-rev-rev' => 'Revisi:', |
| 3083 | + 'code-rev-status' => 'Status:', |
| 3084 | + 'code-rev-comment-preview' => 'Pratayang', |
| 3085 | + 'code-status-new' => 'baru', |
| 3086 | + 'code-status-reverted' => 'telah dikembalikan', |
| 3087 | + 'code-status-ok' => 'ok', |
| 3088 | + 'code-pathsearch-path' => 'Jalan:', |
| 3089 | + 'codereview-batch-submit' => 'Kirim', |
| 3090 | + 'codereview-subtitle' => 'Untuk $1', |
| 3091 | + 'repoadmin-new-button' => 'Buat', |
| 3092 | + 'repoadmin-edit-button' => 'OK', |
| 3093 | +); |
| 3094 | + |
2995 | 3095 | /** Ido (Ido) |
2996 | 3096 | * @author Malafaya |
2997 | 3097 | */ |
— | — | @@ -3126,21 +3226,25 @@ |
3127 | 3227 | */ |
3128 | 3228 | $messages['ja'] = array( |
3129 | 3229 | 'code' => 'コードレビュー', |
3130 | | - 'code-rev-title' => '$1版 - コードレビュー', |
| 3230 | + 'code-rev-title' => '第$1版 - コードレビュー', |
3131 | 3231 | 'code-comments' => 'コメント', |
3132 | 3232 | 'code-references' => '追補版', |
3133 | | - 'code-change-status' => "r$1 の'''ステータス'''を変更しました", |
| 3233 | + 'code-change-status' => "r$1 の'''状態'''を変更しました", |
3134 | 3234 | 'code-change-tags' => "r$1 の'''タグ'''を変更しました", |
3135 | 3235 | 'code-change-removed' => '除去:', |
3136 | 3236 | 'code-change-added' => '追加:', |
3137 | | - 'code-prop-changes' => 'ステータスとタグ付けのログ', |
| 3237 | + 'code-old-status' => '古い状態', |
| 3238 | + 'code-new-status' => '新しい状態', |
| 3239 | + 'code-prop-changes' => '状態とタグ付けのログ', |
3138 | 3240 | 'code-desc' => '[[Special:RepoAdmin|Subversion サポート]]付きの[[Special:Code|コードレビュー・ツール]]', |
3139 | | - 'code-no-repo' => '設定されたリポジトリはありません!', |
| 3241 | + 'code-no-repo' => '設定されたリポジトリはありません!', |
3140 | 3242 | 'code-load-diff' => '差分を読み込み中…', |
3141 | 3243 | 'code-notes' => '最近のコメント', |
| 3244 | + 'code-statuschanges' => '状態の変更', |
3142 | 3245 | 'code-authors' => '著者', |
3143 | | - 'code-status' => 'ステータス', |
| 3246 | + 'code-status' => '状態', |
3144 | 3247 | 'code-tags' => 'タグ', |
| 3248 | + 'code-tests' => 'テストケース', |
3145 | 3249 | 'code-authors-text' => '以下は最新コミット順のリポジトリ作成者一覧です。', |
3146 | 3250 | 'code-author-haslink' => 'この著者はウィキの利用者 $1 と対応付けられています。', |
3147 | 3251 | 'code-author-orphan' => 'この著者にはウィキのアカウントとの対応付けがありません。', |
— | — | @@ -3156,9 +3260,10 @@ |
3157 | 3261 | 'code-field-author' => '著者', |
3158 | 3262 | 'code-field-user' => 'コメンター', |
3159 | 3263 | 'code-field-message' => 'コミット要約', |
3160 | | - 'code-field-status' => '状況', |
| 3264 | + 'code-field-status' => '状態', |
3161 | 3265 | 'code-field-timestamp' => '日付', |
3162 | 3266 | 'code-field-comments' => 'コメント', |
| 3267 | + 'code-field-tests' => 'テスト', |
3163 | 3268 | 'code-field-path' => 'パス', |
3164 | 3269 | 'code-field-text' => 'コメント', |
3165 | 3270 | 'code-field-select' => '選択', |
— | — | @@ -3174,8 +3279,8 @@ |
3175 | 3280 | 'code-rev-modified-d' => '削除', |
3176 | 3281 | 'code-rev-modified-m' => '変更', |
3177 | 3282 | 'code-rev-imagediff' => '画像の変更', |
3178 | | - 'code-rev-status' => 'ステータス:', |
3179 | | - 'code-rev-status-set' => 'ステータスを変更する', |
| 3283 | + 'code-rev-status' => '状態:', |
| 3284 | + 'code-rev-status-set' => '状態を変更する', |
3180 | 3285 | 'code-rev-tags' => 'タグ:', |
3181 | 3286 | 'code-rev-tag-add' => 'タグを追加:', |
3182 | 3287 | 'code-rev-tag-remove' => 'タグを除去:', |
— | — | @@ -3187,7 +3292,7 @@ |
3188 | 3293 | 'code-rev-purge-link' => 'パージ', |
3189 | 3294 | 'code-status-new' => '新規', |
3190 | 3295 | 'code-status-fixme' => '要修正', |
3191 | | - 'code-status-reverted' => '取消済', |
| 3296 | + 'code-status-reverted' => '差し戻し済み', |
3192 | 3297 | 'code-status-resolved' => '解決済', |
3193 | 3298 | 'code-status-ok' => '問題なし', |
3194 | 3299 | 'code-status-verified' => '検証済', |
— | — | @@ -3196,7 +3301,7 @@ |
3197 | 3302 | 'code-pathsearch-path' => 'パス:', |
3198 | 3303 | 'code-rev-submit' => '変更を保存', |
3199 | 3304 | 'code-rev-submit-next' => '保存し、次の未解決に移る', |
3200 | | - 'code-batch-status' => 'ステータスを変更:', |
| 3305 | + 'code-batch-status' => '状態を変更:', |
3201 | 3306 | 'code-batch-tags' => 'タグを変更:', |
3202 | 3307 | 'codereview-batch-title' => '選択したリビジョンをすべて変更する', |
3203 | 3308 | 'codereview-batch-submit' => '送信', |
— | — | @@ -3237,7 +3342,7 @@ |
3238 | 3343 | 'right-codereview-add-tag' => 'リビジョンに新しいタグを追加する', |
3239 | 3344 | 'right-codereview-remove-tag' => 'リビジョンからタグを除去する', |
3240 | 3345 | 'right-codereview-post-comment' => 'リビジョンにコメントを追加する', |
3241 | | - 'right-codereview-set-status' => 'リビジョンのステータスを変更する', |
| 3346 | + 'right-codereview-set-status' => 'リビジョンの状態を変更する', |
3242 | 3347 | 'right-codereview-link-user' => '著者とウィキ利用者を対応付ける', |
3243 | 3348 | 'specialpages-group-developer' => '開発者用ツール', |
3244 | 3349 | ); |
— | — | @@ -3248,14 +3353,14 @@ |
3249 | 3354 | $messages['jv'] = array( |
3250 | 3355 | 'code' => 'Pamriksan kodhe', |
3251 | 3356 | 'code-comments' => 'Komentar', |
3252 | | - 'code-change-status' => "Ngowahi '''status''' révisi iki", |
3253 | | - 'code-change-tags' => "ngowahi ''tag'' révisi iki", |
| 3357 | + 'code-change-status' => "ngowahi '''status''' saka r$1", |
| 3358 | + 'code-change-tags' => "ngowahi '''tag''' saka r$1", |
3254 | 3359 | 'code-change-removed' => 'busak:', |
3255 | 3360 | 'code-change-added' => 'ditambahaké:', |
3256 | 3361 | 'code-prop-changes' => "Log pamasangan ''tag'' & status", |
3257 | 3362 | 'code-desc' => '[[Special:Code|piranti pamriksan kodhe]] kanthi [[Special:RepoAdmin|dhukungan anak-vèrsi]]', |
3258 | 3363 | 'code-no-repo' => "Ora ana panyimpenan (''repository'') kang dipilih (''configured'')", |
3259 | | - 'code-notes' => 'Cathetan pamriksan', |
| 3364 | + 'code-notes' => 'Komentar anyar', |
3260 | 3365 | 'code-authors' => 'pangarang', |
3261 | 3366 | 'code-tags' => "tandha (''tag'')", |
3262 | 3367 | 'code-authors-text' => "Ing ngisor dhaptar pangarang repo miturut ''commit'' sing anyar iki.", |
— | — | @@ -3308,8 +3413,8 @@ |
3309 | 3414 | 'code-status-deferred' => 'ditundha', |
3310 | 3415 | 'code-pathsearch-legend' => 'Golèki révisi ing jalur repo iki', |
3311 | 3416 | 'code-pathsearch-path' => 'Jalur/lintasan', |
3312 | | - 'code-rev-submit' => "Owah-owahan ''commit''", |
3313 | | - 'code-rev-submit-next' => "''Commit'' lan durung bèrès (''unresolved'')", |
| 3417 | + 'code-rev-submit' => 'Simpen owah-owahan', |
| 3418 | + 'code-rev-submit-next' => 'Simpen & durung-bèrès sabanjuré', |
3314 | 3419 | 'codereview-reply-link' => 'wales/walesan', |
3315 | 3420 | 'codereview-email-subj' => '[$1] [r$2]: Komentar anyar ditambahaké', |
3316 | 3421 | 'codereview-email-body' => 'Panganggo "$1" awèh komentar ing r$3. |
— | — | @@ -3530,14 +3635,18 @@ |
3531 | 3636 | 'code-change-tags' => "hät de '''Makeerunge''' vun dä Version $1 verändert", |
3532 | 3637 | 'code-change-removed' => 'eruß jenomme:', |
3533 | 3638 | 'code-change-added' => 'dobei jedonn:', |
| 3639 | + 'code-old-status' => 'Der ahle Stattus', |
| 3640 | + 'code-new-status' => 'Der neue Shtattus', |
3534 | 3641 | 'code-prop-changes' => 'Logboch för Shtattus un Makeerunge', |
3535 | 3642 | 'code-desc' => 'Werkzüch för [[Special:Code|Projramm-Änderunge ze verwallde]] met [[Special:RepoAdmin|Ongershtözung för <i lang="en">Subversion</i>]]', |
3536 | 3643 | 'code-no-repo' => 'Et es kei Repositorijum enjeshtallt.', |
3537 | 3644 | 'code-load-diff' => 'Ben de Ungerscheide aam Lade …', |
3538 | 3645 | 'code-notes' => 'De neuste Bemerkunge', |
| 3646 | + 'code-statuschanges' => 'Änderunge aam Stattus', |
3539 | 3647 | 'code-authors' => 'de Schriiver', |
3540 | | - 'code-status' => 'Shtattus', |
| 3648 | + 'code-status' => 'Shtattuße', |
3541 | 3649 | 'code-tags' => 'Makeerunge', |
| 3650 | + 'code-tests' => 'Prööf-Fäll', |
3542 | 3651 | 'code-authors-text' => 'Hee kütt en Leß met dä Schriever aan dämm Repositorijum, en dä Reijefolch, wie se jespeichert hann.', |
3543 | 3652 | 'code-author-haslink' => 'Dä Schriiver es em Wiki mem Metmaacher $1 verlengk', |
3544 | 3653 | 'code-author-orphan' => 'Dä Schriiver es nit met enem Metmaacher em Wiki verlengk', |
— | — | @@ -3556,6 +3665,7 @@ |
3557 | 3666 | 'code-field-status' => 'Shtattus', |
3558 | 3667 | 'code-field-timestamp' => 'Zick un Dattum', |
3559 | 3668 | 'code-field-comments' => 'Bemerkunge', |
| 3669 | + 'code-field-tests' => 'Pröfunge', |
3560 | 3670 | 'code-field-path' => 'Pad', |
3561 | 3671 | 'code-field-text' => 'Notiz', |
3562 | 3672 | 'code-field-select' => 'Ußsöke', |
— | — | @@ -3640,6 +3750,7 @@ |
3641 | 3751 | ); |
3642 | 3752 | |
3643 | 3753 | /** Luxembourgish (Lëtzebuergesch) |
| 3754 | + * @author Les Meloures |
3644 | 3755 | * @author Robby |
3645 | 3756 | */ |
3646 | 3757 | $messages['lb'] = array( |
— | — | @@ -3651,12 +3762,15 @@ |
3652 | 3763 | 'code-change-tags' => "huet '''Taggen''' fir $1 geännert", |
3653 | 3764 | 'code-change-removed' => 'ewech geholl:', |
3654 | 3765 | 'code-change-added' => 'derbäi gesat:', |
| 3766 | + 'code-old-status' => 'Ale Status', |
| 3767 | + 'code-new-status' => 'Neie Status', |
3655 | 3768 | 'code-prop-changes' => 'Logbuch vum Status an den Taggen', |
3656 | | - 'code-desc' => "[[Special:Code|Tool fir de Code nozekucken]] matt [[Special:RepoAdmin|Subversioun's Ënnerstëtzung]]", |
| 3769 | + 'code-desc' => "[[Special:Code|Tool fir de Code nozekucken]] mat [[Special:RepoAdmin|Subversioun's Ënnerstëtzung]]", |
3657 | 3770 | 'code-load-diff' => 'Lude vun Diff…', |
3658 | 3771 | 'code-notes' => 'rezent Bemierkungen', |
| 3772 | + 'code-statuschanges' => 'Ännerunge vum Status', |
3659 | 3773 | 'code-authors' => 'Auteuren', |
3660 | | - 'code-status' => 'Status', |
| 3774 | + 'code-status' => 'Statussen', |
3661 | 3775 | 'code-tags' => 'Tagen', |
3662 | 3776 | 'code-author-haslink' => 'Dësen Auteur ass mam Wiki-Benotzer $1 verbonn', |
3663 | 3777 | 'code-author-orphan' => 'Dëse Benotzer huet kee Link mat engem Wiki-Benotzerkont', |
— | — | @@ -3683,6 +3797,7 @@ |
3684 | 3798 | 'code-rev-message' => 'Bemierkung:', |
3685 | 3799 | 'code-rev-rev' => 'Versioun:', |
3686 | 3800 | 'code-rev-rev-viewvc' => 'op ViewVC', |
| 3801 | + 'code-rev-paths' => 'Geännert Pied:', |
3687 | 3802 | 'code-rev-modified-a' => 'derbäigesat', |
3688 | 3803 | 'code-rev-modified-r' => 'ersat', |
3689 | 3804 | 'code-rev-modified-d' => 'geläscht', |
— | — | @@ -3733,8 +3848,8 @@ |
3734 | 3849 | 'repoadmin-edit-button' => 'OK', |
3735 | 3850 | 'repoadmin-edit-sucess' => 'De \'\'Repositoire\'\' "[[Special:Code/$1|$1]]" gouf geännert.', |
3736 | 3851 | 'right-codereview-use' => 'Spezial benotzen: Code', |
3737 | | - 'right-codereview-add-tag' => "Nei Tagen bäi d'Versiounen derbäisetzen", |
3738 | | - 'right-codereview-remove-tag' => 'Taggen aus Versiounen eraushuelen', |
| 3852 | + 'right-codereview-add-tag' => "Nei Markéierunge bei d'Versiounen derbäisetzen", |
| 3853 | + 'right-codereview-remove-tag' => 'Markéierungen aus Versiounen eraushuelen', |
3739 | 3854 | 'right-codereview-post-comment' => "Bemierkunge Bài d'Versiounen derbäisetzen", |
3740 | 3855 | 'right-codereview-set-status' => 'Ännere vum Status vun de Versiounen', |
3741 | 3856 | 'right-codereview-link-user' => 'Auteure mat Wiki-Benotzer verbannen (verlinken)', |
— | — | @@ -3874,6 +3989,14 @@ |
3875 | 3990 | 'specialpages-group-developer' => 'Hölpmiddele veur óntwikkeleers', |
3876 | 3991 | ); |
3877 | 3992 | |
| 3993 | +/** Lithuanian (Lietuvių) |
| 3994 | + * @author Matasg |
| 3995 | + */ |
| 3996 | +$messages['lt'] = array( |
| 3997 | + 'repoadmin-new-button' => 'Sukurti', |
| 3998 | + 'repoadmin-edit-button' => 'Gerai', |
| 3999 | +); |
| 4000 | + |
3878 | 4001 | /** Macedonian (Македонски) |
3879 | 4002 | * @author Brest |
3880 | 4003 | */ |
— | — | @@ -4189,14 +4312,18 @@ |
4190 | 4313 | 'code-change-tags' => "heeft de '''labels''' voor versie r$1 gewijzigd", |
4191 | 4314 | 'code-change-removed' => 'verwijderd:', |
4192 | 4315 | 'code-change-added' => 'toegevoegd:', |
| 4316 | + 'code-old-status' => 'Oude status', |
| 4317 | + 'code-new-status' => 'Nieuwe status', |
4193 | 4318 | 'code-prop-changes' => 'Logboek status en labels', |
4194 | 4319 | 'code-desc' => '[[Special:Code|Hulpprogramma voor codecontrole]] met [[Special:RepoAdmin|ondersteuning voor Subversion]]', |
4195 | 4320 | 'code-no-repo' => 'Er is geen repository ingesteld!', |
4196 | 4321 | 'code-load-diff' => 'Bezig met het laden van de veranderingen…', |
4197 | 4322 | 'code-notes' => 'recente opmerkingen', |
| 4323 | + 'code-statuschanges' => 'statuswijzigingen', |
4198 | 4324 | 'code-authors' => 'auteurs', |
4199 | | - 'code-status' => 'status', |
| 4325 | + 'code-status' => 'statussen', |
4200 | 4326 | 'code-tags' => 'labels', |
| 4327 | + 'code-tests' => 'Testcases', |
4201 | 4328 | 'code-authors-text' => 'Hieronder staat een lijst met auteurs uit de repository, degene met de meest recente commit bovenaan.', |
4202 | 4329 | 'code-author-haslink' => 'Deze auteur is gekoppeld aan de wikigebruiker $1', |
4203 | 4330 | 'code-author-orphan' => 'Deze auteur is niet gekoppeld aan een wikigebruiker', |
— | — | @@ -4215,6 +4342,7 @@ |
4216 | 4343 | 'code-field-status' => 'Status', |
4217 | 4344 | 'code-field-timestamp' => 'Datum', |
4218 | 4345 | 'code-field-comments' => 'Aantekeningen', |
| 4346 | + 'code-field-tests' => 'Tests', |
4219 | 4347 | 'code-field-path' => 'Pad', |
4220 | 4348 | 'code-field-text' => 'Opmerking', |
4221 | 4349 | 'code-field-select' => 'Selecteren', |
— | — | @@ -4681,7 +4809,16 @@ |
4682 | 4810 | * @author Xqt |
4683 | 4811 | */ |
4684 | 4812 | $messages['pdc'] = array( |
| 4813 | + 'code-comments' => 'Comments', |
| 4814 | + 'code-references' => 'Neegschte Versione', |
| 4815 | + 'code-change-removed' => 'gelöscht:', |
| 4816 | + 'code-change-added' => 'dezu geduh:', |
| 4817 | + 'code-rev-message' => 'Comment:', |
| 4818 | + 'code-rev-modified-d' => 'gelöscht', |
| 4819 | + 'code-status-new' => 'nei', |
| 4820 | + 'code-status-ok' => 'OK', |
4685 | 4821 | 'codereview-subtitle' => 'Fer $1', |
| 4822 | + 'repoadmin-edit-button' => 'OK', |
4686 | 4823 | ); |
4687 | 4824 | |
4688 | 4825 | /** Polish (Polski) |
— | — | @@ -4699,13 +4836,16 @@ |
4700 | 4837 | 'code-change-tags' => "zmieniono '''znaczniki''' r$1", |
4701 | 4838 | 'code-change-removed' => 'usunięto:', |
4702 | 4839 | 'code-change-added' => 'dodano:', |
| 4840 | + 'code-old-status' => 'Poprzedni status', |
| 4841 | + 'code-new-status' => 'Nowy status', |
4703 | 4842 | 'code-prop-changes' => 'Rejestr zmian statusu i znaczników', |
4704 | 4843 | 'code-desc' => '[[Special:Code|Narzędzie do przeglądania]] oraz [[Special:RepoAdmin|zarządzania wersjami]] kodu źródłowego', |
4705 | 4844 | 'code-no-repo' => 'Brak skonfigurowanego repozytorium!', |
4706 | 4845 | 'code-load-diff' => 'Ładowanie różnic…', |
4707 | 4846 | 'code-notes' => 'ostatnie komentarze', |
| 4847 | + 'code-statuschanges' => 'zmiany statusu', |
4708 | 4848 | 'code-authors' => 'autorzy', |
4709 | | - 'code-status' => 'status', |
| 4849 | + 'code-status' => 'statusy', |
4710 | 4850 | 'code-tags' => 'znaczniki', |
4711 | 4851 | 'code-authors-text' => 'Poniżej znajduje się lista autorów repozytorium w kolejności ostatnio dodanej poprawki do kodu źródłowego.', |
4712 | 4852 | 'code-author-haslink' => 'Ten autor jest podlinkowany do konta użytkownika na wiki jako $1', |
— | — | @@ -5106,9 +5246,13 @@ |
5107 | 5247 | * @author Joetaras |
5108 | 5248 | */ |
5109 | 5249 | $messages['roa-tara'] = array( |
| 5250 | + 'code' => 'Revisore de Codece', |
| 5251 | + 'code-rev-title' => 'r$1 - Revisore de Codece', |
5110 | 5252 | 'code-comments' => 'Commende', |
5111 | 5253 | 'code-change-removed' => 'luete:', |
5112 | 5254 | 'code-change-added' => 'aggiunde:', |
| 5255 | + 'code-old-status' => 'State vecchije', |
| 5256 | + 'code-new-status' => 'State nuève', |
5113 | 5257 | 'code-authors' => 'le autore', |
5114 | 5258 | 'code-status' => 'state', |
5115 | 5259 | 'code-tags' => 'le tag', |
— | — | @@ -5157,6 +5301,7 @@ |
5158 | 5302 | ); |
5159 | 5303 | |
5160 | 5304 | /** Russian (Русский) |
| 5305 | + * @author Ferrer |
5161 | 5306 | * @author Kaganer |
5162 | 5307 | * @author Putnik |
5163 | 5308 | * @author Александр Сигачёв |
— | — | @@ -5170,14 +5315,18 @@ |
5171 | 5316 | 'code-change-tags' => "изменил '''метки''' для r$1", |
5172 | 5317 | 'code-change-removed' => 'удалено:', |
5173 | 5318 | 'code-change-added' => 'добавлено:', |
| 5319 | + 'code-old-status' => 'Старый статус', |
| 5320 | + 'code-new-status' => 'Новый статус', |
5174 | 5321 | 'code-prop-changes' => 'Журнал статусов и меток', |
5175 | 5322 | 'code-desc' => '[[Special:Code|Инструмент проверки кода]] с [[Special:RepoAdmin|поддержкой Subversion]]', |
5176 | 5323 | 'code-no-repo' => 'Отсутствует настроенное хранилище!', |
5177 | 5324 | 'code-load-diff' => 'Загрузка сравнения…', |
5178 | 5325 | 'code-notes' => 'последние замечания', |
| 5326 | + 'code-statuschanges' => 'изменения статуса', |
5179 | 5327 | 'code-authors' => 'авторы', |
5180 | | - 'code-status' => 'состояние', |
| 5328 | + 'code-status' => 'состояния', |
5181 | 5329 | 'code-tags' => 'метки', |
| 5330 | + 'code-tests' => 'Тестовые запросы', |
5182 | 5331 | 'code-authors-text' => 'Ниже находится список авторов в порядке свежести вносимых ими изменений (более новые — сверху).', |
5183 | 5332 | 'code-author-haslink' => 'Этот автор ассоциирован с участником $1', |
5184 | 5333 | 'code-author-orphan' => 'Для этого автора не установлена связь с учётной записью вики-проекта', |
— | — | @@ -5196,6 +5345,7 @@ |
5197 | 5346 | 'code-field-status' => 'Статус', |
5198 | 5347 | 'code-field-timestamp' => 'Дата', |
5199 | 5348 | 'code-field-comments' => 'Комментариев', |
| 5349 | + 'code-field-tests' => 'Тесты', |
5200 | 5350 | 'code-field-path' => 'Путь', |
5201 | 5351 | 'code-field-text' => 'Замечание', |
5202 | 5352 | 'code-field-select' => 'Выбрать', |
— | — | @@ -5291,13 +5441,16 @@ |
5292 | 5442 | 'code-change-tags' => "'''бэлиэлэрин''' $1 уларыппыт (бэлиэлэрэ уларыйбыт)", |
5293 | 5443 | 'code-change-removed' => 'сотулунна:', |
5294 | 5444 | 'code-change-added' => 'эбилиннэ:', |
| 5445 | + 'code-old-status' => 'Урукку туруга', |
| 5446 | + 'code-new-status' => 'Саҥа туруга', |
5295 | 5447 | 'code-prop-changes' => 'Статус уонна бэлиэлэр сурунааллара', |
5296 | 5448 | 'code-desc' => '[[Special:RepoAdmin|Subversion]] өйүүр [[Special:Code|куоду көрдөрөр үнүстүрүмүөн]]', |
5297 | 5449 | 'code-no-repo' => 'Анаан оҥоһуллубут ыскылаат суох', |
5298 | 5450 | 'code-load-diff' => 'Тэҥнээһин...', |
5299 | 5451 | 'code-notes' => 'соторутааҥҥы бэлиэтээһиннэр', |
| 5452 | + 'code-statuschanges' => 'турук уларыйыылара', |
5300 | 5453 | 'code-authors' => 'ааптардар', |
5301 | | - 'code-status' => 'туруга (стаатуһа)', |
| 5454 | + 'code-status' => 'туруктара (статустара)', |
5302 | 5455 | 'code-tags' => 'бэлиэлэр', |
5303 | 5456 | 'code-authors-text' => 'Аллараа ааптардар тиһиктэрэ киллэрбит уларытыыларын кэминэн наарданан (саҥалар — үөһэ) бэриллэр.', |
5304 | 5457 | 'code-author-haslink' => 'Бу ааптар $1 кыттааччыга сигэнэр', |
— | — | @@ -5517,13 +5670,16 @@ |
5518 | 5671 | 'code-change-tags' => "zmenil '''značky''' r$1", |
5519 | 5672 | 'code-change-removed' => 'odstránené:', |
5520 | 5673 | 'code-change-added' => 'pridané:', |
| 5674 | + 'code-old-status' => 'Starý stav', |
| 5675 | + 'code-new-status' => 'Nový stav', |
5521 | 5676 | 'code-prop-changes' => 'Záznam stavu a značiek', |
5522 | 5677 | 'code-desc' => '[[Special:Code|Nástroj na kontrolu kódu]] s [[Special:RepoAdmin|podporou Subversion]]', |
5523 | 5678 | 'code-no-repo' => 'Nebolo nastavené žiadne úložisko', |
5524 | 5679 | 'code-load-diff' => 'Načítava sa rozdiel…', |
5525 | 5680 | 'code-notes' => 'posledné komentáre', |
| 5681 | + 'code-statuschanges' => 'zmeny stavu', |
5526 | 5682 | 'code-authors' => 'autori', |
5527 | | - 'code-status' => 'stav', |
| 5683 | + 'code-status' => 'stavy', |
5528 | 5684 | 'code-tags' => 'značky', |
5529 | 5685 | 'code-authors-text' => 'Toto je zoznam autorov v úložisku v poradí podľa posledných commitov.', |
5530 | 5686 | 'code-author-haslink' => 'Tento autor je zviazaný s používateľom wiki $1', |
— | — | @@ -6725,7 +6881,7 @@ |
6726 | 6882 | */ |
6727 | 6883 | $messages['vo'] = array( |
6728 | 6884 | 'code-comments' => 'Küpets', |
6729 | | - 'code-change-status' => "evotükon '''stadi''' revida at", |
| 6885 | + 'code-change-status' => "evotükon '''stadi''' ela r$1", |
6730 | 6886 | 'code-change-removed' => 'pemoükon:', |
6731 | 6887 | 'code-change-added' => 'peläükon:', |
6732 | 6888 | 'code-no-repo' => 'No dabinon kipedöp labü paramets pegivülöl!', |
Index: branches/wmf-deployment/extensions/CodeReview/CodeReview.php |
— | — | @@ -37,32 +37,41 @@ |
38 | 38 | |
39 | 39 | $dir = dirname( __FILE__ ) . '/'; |
40 | 40 | |
41 | | -$wgAutoloadClasses['ApiCodeUpdate'] = $dir . 'ApiCodeUpdate.php'; |
42 | | -$wgAutoloadClasses['ApiCodeDiff'] = $dir . 'ApiCodeDiff.php'; |
43 | | -$wgAutoloadClasses['ApiCodeComments'] = $dir . 'ApiCodeComments.php'; |
44 | | -$wgAutoloadClasses['CodeDiffHighlighter'] = $dir . 'DiffHighlighter.php'; |
45 | | -$wgAutoloadClasses['CodeRepository'] = $dir . 'CodeRepository.php'; |
46 | | -$wgAutoloadClasses['CodeRepoListView'] = $dir . 'CodeRepoListView.php'; |
47 | | -$wgAutoloadClasses['CodeRevision'] = $dir . 'CodeRevision.php'; |
48 | | -$wgAutoloadClasses['CodeRevisionAuthorView'] = $dir . 'CodeRevisionAuthorView.php'; |
49 | | -$wgAutoloadClasses['CodeRevisionAuthorLink'] = $dir . 'CodeRevisionAuthorLink.php'; |
50 | | -$wgAutoloadClasses['CodeRevisionListView'] = $dir . 'CodeRevisionListView.php'; |
51 | | -$wgAutoloadClasses['CodeRevisionCommitter'] = $dir . 'CodeRevisionCommitter.php'; |
52 | | -$wgAutoloadClasses['CodeRevisionStatusView'] = $dir . 'CodeRevisionStatusView.php'; |
53 | | -$wgAutoloadClasses['CodeRevisionTagView'] = $dir . 'CodeRevisionTagView.php'; |
54 | | -$wgAutoloadClasses['CodeRevisionView'] = $dir . 'CodeRevisionView.php'; |
55 | | -$wgAutoloadClasses['CodeAuthorListView'] = $dir . 'CodeAuthorListView.php'; |
56 | | -$wgAutoloadClasses['CodeStatusListView'] = $dir . 'CodeStatusListView.php'; |
57 | | -$wgAutoloadClasses['CodeTagListView'] = $dir . 'CodeTagListView.php'; |
58 | | -$wgAutoloadClasses['CodeCommentsListView'] = $dir . 'CodeCommentsListView.php'; |
59 | | -$wgAutoloadClasses['CodeReleaseNotes'] = $dir . 'CodeReleaseNotes.php'; |
60 | | -$wgAutoloadClasses['CodeComment'] = $dir . 'CodeComment.php'; |
61 | | -$wgAutoloadClasses['CodePropChange'] = $dir . 'CodePropChange.php'; |
62 | | -$wgAutoloadClasses['SpecialCode'] = $dir . 'SpecialCode.php'; |
63 | | -$wgAutoloadClasses['CodeView'] = $dir . 'SpecialCode.php'; |
64 | | -$wgAutoloadClasses['SpecialRepoAdmin'] = $dir . 'SpecialRepoAdmin.php'; |
65 | | -$wgAutoloadClasses['SubversionAdaptor'] = $dir . 'Subversion.php'; |
| 41 | +$wgAutoloadClasses['ApiCodeUpdate'] = $dir . 'api/ApiCodeUpdate.php'; |
| 42 | +$wgAutoloadClasses['ApiCodeDiff'] = $dir . 'api/ApiCodeDiff.php'; |
| 43 | +$wgAutoloadClasses['ApiCodeComments'] = $dir . 'api/ApiCodeComments.php'; |
| 44 | +$wgAutoloadClasses['ApiCodeTestUpload'] = $dir . 'api/ApiCodeTestUpload.php'; |
66 | 45 | |
| 46 | +$wgAutoloadClasses['SubversionAdaptor'] = $dir . 'backend/Subversion.php'; |
| 47 | +$wgAutoloadClasses['CodeDiffHighlighter'] = $dir . 'backend/DiffHighlighter.php'; |
| 48 | + |
| 49 | +$wgAutoloadClasses['CodeRepository'] = $dir . 'backend/CodeRepository.php'; |
| 50 | +$wgAutoloadClasses['CodeRevision'] = $dir . 'backend/CodeRevision.php'; |
| 51 | +$wgAutoloadClasses['CodeComment'] = $dir . 'backend/CodeComment.php'; |
| 52 | +$wgAutoloadClasses['CodePropChange'] = $dir . 'backend/CodePropChange.php'; |
| 53 | +$wgAutoloadClasses['CodeTestSuite'] = $dir . 'backend/CodeTestSuite.php'; |
| 54 | +$wgAutoloadClasses['CodeTestRun'] = $dir . 'backend/CodeTestRun.php'; |
| 55 | +$wgAutoloadClasses['CodeTestResult'] = $dir . 'backend/CodeTestResult.php'; |
| 56 | + |
| 57 | +$wgAutoloadClasses['CodeRepoListView'] = $dir . 'ui/CodeRepoListView.php'; |
| 58 | +$wgAutoloadClasses['CodeRevisionAuthorView'] = $dir . 'ui/CodeRevisionAuthorView.php'; |
| 59 | +$wgAutoloadClasses['CodeRevisionAuthorLink'] = $dir . 'ui/CodeRevisionAuthorLink.php'; |
| 60 | +$wgAutoloadClasses['CodeRevisionCommitter'] = $dir . 'ui/CodeRevisionCommitter.php'; |
| 61 | +$wgAutoloadClasses['CodeRevisionListView'] = $dir . 'ui/CodeRevisionListView.php'; |
| 62 | +$wgAutoloadClasses['CodeRevisionStatusView'] = $dir . 'ui/CodeRevisionStatusView.php'; |
| 63 | +$wgAutoloadClasses['CodeRevisionTagView'] = $dir . 'ui/CodeRevisionTagView.php'; |
| 64 | +$wgAutoloadClasses['CodeRevisionView'] = $dir . 'ui/CodeRevisionView.php'; |
| 65 | +$wgAutoloadClasses['CodeAuthorListView'] = $dir . 'ui/CodeAuthorListView.php'; |
| 66 | +$wgAutoloadClasses['CodeStatusListView'] = $dir . 'ui/CodeStatusListView.php'; |
| 67 | +$wgAutoloadClasses['CodeTagListView'] = $dir . 'ui/CodeTagListView.php'; |
| 68 | +$wgAutoloadClasses['CodeCommentsListView'] = $dir . 'ui/CodeCommentsListView.php'; |
| 69 | +$wgAutoloadClasses['CodeReleaseNotes'] = $dir . 'ui/CodeReleaseNotes.php'; |
| 70 | +$wgAutoloadClasses['CodeStatusChangeListView'] = $dir . 'ui/CodeStatusChangeListView.php'; |
| 71 | +$wgAutoloadClasses['SpecialCode'] = $dir . 'ui/SpecialCode.php'; |
| 72 | +$wgAutoloadClasses['CodeView'] = $dir . 'ui/SpecialCode.php'; |
| 73 | +$wgAutoloadClasses['SpecialRepoAdmin'] = $dir . 'ui/SpecialRepoAdmin.php'; |
| 74 | + |
| 75 | + |
67 | 76 | $wgSpecialPages['Code'] = 'SpecialCode'; |
68 | 77 | $wgSpecialPageGroups['Code'] = 'developer'; |
69 | 78 | $wgSpecialPages['RepoAdmin'] = 'SpecialRepoAdmin'; |
— | — | @@ -70,6 +79,7 @@ |
71 | 80 | |
72 | 81 | $wgAPIModules['codeupdate'] = 'ApiCodeUpdate'; |
73 | 82 | $wgAPIModules['codediff'] = 'ApiCodeDiff'; |
| 83 | +$wgAPIModules['codetestupload'] = 'ApiCodeTestUpload'; |
74 | 84 | $wgAPIListModules['codecomments'] = 'ApiCodeComments'; |
75 | 85 | |
76 | 86 | $wgExtensionMessagesFiles['CodeReview'] = $dir . 'CodeReview.i18n.php'; |
— | — | @@ -99,6 +109,12 @@ |
100 | 110 | $wgSubversionProxy = false; |
101 | 111 | $wgSubversionProxyTimeout = 30; // default 3 secs is too short :) |
102 | 112 | |
| 113 | +// Command-line options to pass on SVN command line if SVN PECL extension |
| 114 | +// isn't available and we're not using the proxy. |
| 115 | +// Defaults here should allow working with both http: and https: repos |
| 116 | +// as long as authentication isn't required. |
| 117 | +$wgSubversionOptions = "--non-interactive --trust-server-cert"; |
| 118 | + |
103 | 119 | // What is the default SVN import chunk size? |
104 | 120 | $wgCodeReviewImportBatchSize = 400; |
105 | 121 | |
— | — | @@ -119,3 +135,27 @@ |
120 | 136 | |
121 | 137 | // What images can be used for client-side side-by-side comparisons? |
122 | 138 | $wgCodeReviewImgRegex = '/\.(png|jpg|jpeg|gif)$/i'; |
| 139 | + |
| 140 | +// Set to a secret string for HMAC validation of test run data uploads. |
| 141 | +// Should match test runner's $wgParserTestRemote['secret']. |
| 142 | +$wgCodeReviewSharedSecret = false; |
| 143 | + |
| 144 | +# Schema changes |
| 145 | +$wgHooks['LoadExtensionSchemaUpdates'][] = 'efCodeReviewSchemaUpdates'; |
| 146 | + |
| 147 | +function efCodeReviewSchemaUpdates() { |
| 148 | + global $wgDBtype, $wgExtNewFields, $wgExtPGNewFields, $wgExtNewIndexes, $wgExtNewTables; |
| 149 | + $base = dirname(__FILE__); |
| 150 | + if( $wgDBtype == 'mysql' ) { |
| 151 | + $wgExtNewTables[] = array( 'code_rev', "$base/codereview.sql" ); // Initial install tables |
| 152 | + $wgExtNewFields[] = array( 'code_rev', 'cr_diff', "$base/archives/codereview-cr_diff.sql" ); |
| 153 | + $wgExtNewIndexes[] = array( 'code_relations', 'repo_to_from', "$base/archives/code_relations_index.sql" ); |
| 154 | + //$wgExtNewFields[] = array( 'code_rev', "$base/archives/codereview-cr_status.sql" ); // FIXME FIXME this is a change to options... don't know how |
| 155 | + $wgExtNewTables[] = array( 'code_bugs', "$base/archives/code_bugs.sql" ); |
| 156 | + $wgExtNewTables[] = array( 'code_test_suite', "$base/archives/codereview-code_tests.sql" ); |
| 157 | + } elseif( $wgDBtype == 'postgres' ) { |
| 158 | + // TODO |
| 159 | + } |
| 160 | + return true; |
| 161 | +} |
| 162 | + |
Index: branches/wmf-deployment/extensions/CodeReview/codereview.css |
— | — | @@ -78,6 +78,13 @@ |
79 | 79 | color: #666; |
80 | 80 | } |
81 | 81 | |
| 82 | +.mw-codereview-success { |
| 83 | + color: #1a2; |
| 84 | +} |
| 85 | +.mw-codereview-fail { |
| 86 | + color: #d21; |
| 87 | +} |
| 88 | + |
82 | 89 | /* Diffs */ |
83 | 90 | .mw-codereview-diff ins { |
84 | 91 | text-decoration: none; |
Index: branches/wmf-deployment/extensions/CodeReview/archives/code_relations_index.sql |
— | — | @@ -1,2 +1,2 @@ |
2 | 2 | ALTER TABLE /*$wgDBprefix*/code_relations |
3 | | - ADD key (cf_repo_id, cf_to, cf_from); |
| 3 | + ADD key repo_to_from (cf_repo_id, cf_to, cf_from); |
Index: branches/wmf-deployment/extensions/CodeReview/archives/codereview-code_tests.sql |
— | — | @@ -0,0 +1,66 @@ |
| 2 | +-- |
| 3 | +-- Information on available test suites |
| 4 | +DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_suite; |
| 5 | +CREATE TABLE /*$wgDBprefix*/code_test_suite ( |
| 6 | + -- Unique ID per test suite |
| 7 | + ctsuite_id int auto_increment not null, |
| 8 | + |
| 9 | + -- Repository ID of the code base this applies to |
| 10 | + ctsuite_repo_id int not null, |
| 11 | + |
| 12 | + -- Which branch path this applies to, eg '/trunk/phase3' |
| 13 | + ctsuite_branch_path varchar(255) not null, |
| 14 | + |
| 15 | + -- Pleasantly user-readable name, eg "ParserTests" |
| 16 | + ctsuite_name varchar(255) not null, |
| 17 | + |
| 18 | + -- Description... |
| 19 | + ctsuite_desc varchar(255) not null, |
| 20 | + |
| 21 | + primary key ctsuite_id (ctsuite_id) |
| 22 | +) /*$wgDBtableOptions*/; |
| 23 | + |
| 24 | +DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_case; |
| 25 | +CREATE TABLE /*$wgDBprefix*/code_test_case ( |
| 26 | + ctcase_id int auto_increment not null, |
| 27 | + ctcase_suite_id int not null, |
| 28 | + ctcase_name varchar(255) not null, |
| 29 | + |
| 30 | + primary key ctc_id (ctcase_id), |
| 31 | + key (ctcase_suite_id, ctcase_id) |
| 32 | +) /*$wgDBtableOptions*/; |
| 33 | + |
| 34 | +DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_run; |
| 35 | +CREATE TABLE /*$wgDBprefix*/code_test_run ( |
| 36 | + ctrun_id int auto_increment not null, |
| 37 | + |
| 38 | + ctrun_suite_id int not null, |
| 39 | + ctrun_rev_id int not null, |
| 40 | + |
| 41 | + ctrun_status enum ('running', 'complete', 'abort'), |
| 42 | + |
| 43 | + ctrun_count_total int, |
| 44 | + ctrun_count_success int, |
| 45 | + |
| 46 | + primary key ctrun_id (ctrun_id), |
| 47 | + key suite_rev (ctrun_suite_id, ctrun_rev_id) |
| 48 | +) /*$wgDBtableOptions*/; |
| 49 | + |
| 50 | + |
| 51 | +DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_result; |
| 52 | +CREATE TABLE /*$wgDBprefix*/code_test_result ( |
| 53 | + ctresult_id int auto_increment not null, |
| 54 | + |
| 55 | + -- Which test run and case are we on? |
| 56 | + ctresult_run_id int not null, |
| 57 | + ctresult_case_id int not null, |
| 58 | + |
| 59 | + -- Did we succeed or fail? |
| 60 | + ctresult_success bool not null, |
| 61 | + |
| 62 | + -- Optional HTML chunk data |
| 63 | + ctresult_details blob, |
| 64 | + |
| 65 | + primary key ctr_id (ctresult_id), |
| 66 | + key run_id (ctresult_run_id, ctresult_id) |
| 67 | +) /*$wgDBtableOptions*/; |
Property changes on: branches/wmf-deployment/extensions/CodeReview/archives/codereview-code_tests.sql |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 68 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/backend/CodeComment.php |
— | — | @@ -0,0 +1,29 @@ |
| 2 | +<?php |
| 3 | +if ( !defined( 'MEDIAWIKI' ) ) die(); |
| 4 | + |
| 5 | +class CodeComment { |
| 6 | + function __construct( $rev ) { |
| 7 | + $this->rev = $rev; |
| 8 | + } |
| 9 | + |
| 10 | + static function newFromRow( $rev, $row ) { |
| 11 | + return self::newFromData( $rev, get_object_vars( $row ) ); |
| 12 | + } |
| 13 | + |
| 14 | + static function newFromData( $rev, $data ) { |
| 15 | + $comment = new CodeComment( $rev ); |
| 16 | + $comment->id = intval( $data['cc_id'] ); |
| 17 | + $comment->text = $data['cc_text']; // fixme |
| 18 | + $comment->user = $data['cc_user']; |
| 19 | + $comment->userText = $data['cc_user_text']; |
| 20 | + $comment->timestamp = wfTimestamp( TS_MW, $data['cc_timestamp'] ); |
| 21 | + $comment->review = $data['cc_review']; |
| 22 | + $comment->sortkey = $data['cc_sortkey']; |
| 23 | + return $comment; |
| 24 | + } |
| 25 | + |
| 26 | + function threadDepth() { |
| 27 | + $timestamps = explode( ",", $this->sortkey ); |
| 28 | + return count( $timestamps ); |
| 29 | + } |
| 30 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/CodeComment.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 31 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/backend/CodeTestRun.php |
— | — | @@ -0,0 +1,170 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class CodeTestRun { |
| 5 | + public function __construct( CodeTestSuite $suite, $row ) { |
| 6 | + if( is_object( $row ) ) { |
| 7 | + $row = wfObjectToArray( $row ); |
| 8 | + } |
| 9 | + $this->suite = $suite; |
| 10 | + $this->id = intval( $row['ctrun_id'] ); |
| 11 | + $this->revId = intval( $row['ctrun_rev_id'] ); |
| 12 | + $this->status = $row['ctrun_status']; |
| 13 | + $this->countTotal = $row['ctrun_count_total']; |
| 14 | + $this->countSuccess = $row['ctrun_count_success']; |
| 15 | + |
| 16 | + $this->mCaseMap = null; // Lazy-initialize... |
| 17 | + } |
| 18 | + |
| 19 | + public function getResults( $success=null ) { |
| 20 | + $dbr = wfGetDB( DB_MASTER ); |
| 21 | + $conds = array( |
| 22 | + 'ctresult_run_id' => $this->id, |
| 23 | + 'ctresult_case_id=ctcase_id', |
| 24 | + ); |
| 25 | + if( $success !== null ) { |
| 26 | + $conds['ctresult_success'] = $success ? 1 : 0; |
| 27 | + } |
| 28 | + |
| 29 | + $result = $dbr->select( |
| 30 | + array( |
| 31 | + 'code_test_result', |
| 32 | + 'code_test_case', |
| 33 | + ), |
| 34 | + '*', |
| 35 | + $conds, |
| 36 | + __METHOD__ ); |
| 37 | + |
| 38 | + $out = array(); |
| 39 | + foreach( $result as $row ) { |
| 40 | + $out[] = new CodeTestResult( $this, $row ); |
| 41 | + } |
| 42 | + return $out; |
| 43 | + } |
| 44 | + |
| 45 | + public static function newFromRevId( CodeTestSuite $suite, $revId ) { |
| 46 | + $dbr = wfGetDB( DB_MASTER ); |
| 47 | + $row = $dbr->selectRow( 'code_test_run', |
| 48 | + '*', |
| 49 | + array( |
| 50 | + 'ctrun_suite_id' => $suite->id, |
| 51 | + 'ctrun_rev_id' => $revId, |
| 52 | + ), |
| 53 | + __METHOD__ ); |
| 54 | + if( $row ) { |
| 55 | + return new CodeTestRun( $suite, $row ); |
| 56 | + } else { |
| 57 | + return null; |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + public function setStatus( $status ) { |
| 62 | + $this->status = $status; |
| 63 | + $dbw = wfGetDB( DB_MASTER ); |
| 64 | + $dbw->update( |
| 65 | + 'code_test_run', |
| 66 | + array( |
| 67 | + 'ctrun_status' => $status, |
| 68 | + ), |
| 69 | + array( |
| 70 | + 'ctrun_id' => $this->id, |
| 71 | + ), |
| 72 | + __METHOD__ ); |
| 73 | + } |
| 74 | + |
| 75 | + public static function insertRun( CodeTestSuite $suite, $revId, $status, $results=array() ) { |
| 76 | + $dbw = wfGetDB( DB_MASTER ); |
| 77 | + $countTotal = count( $results ); |
| 78 | + $countSucceeded = count( array_filter( $results ) ); |
| 79 | + |
| 80 | + $insertData = array( |
| 81 | + 'ctrun_suite_id' => $suite->id, |
| 82 | + 'ctrun_rev_id' => $revId, |
| 83 | + 'ctrun_status' => $status, |
| 84 | + 'ctrun_count_total' => $countTotal, |
| 85 | + 'ctrun_count_success' => $countSucceeded, |
| 86 | + ); |
| 87 | + $dbw->insert( 'code_test_run', |
| 88 | + $insertData, |
| 89 | + __METHOD__ ); |
| 90 | + |
| 91 | + $insertData['ctrun_id'] = $dbw->insertId(); |
| 92 | + $run = new CodeTestRun( $suite, $insertData ); |
| 93 | + if( $status == 'complete' && $results ) { |
| 94 | + $run->insertData( $results ); |
| 95 | + } |
| 96 | + return $run; |
| 97 | + } |
| 98 | + |
| 99 | + public function getCaseId( $caseName ) { |
| 100 | + $this->loadCaseMap(); |
| 101 | + if( isset( $this->mCaseMap[$caseName] ) ) { |
| 102 | + return $this->mCaseMap[$caseName]; |
| 103 | + } else { |
| 104 | + $dbw = wfGetDB( DB_MASTER ); |
| 105 | + $dbw->insert( 'code_test_case', |
| 106 | + array( |
| 107 | + 'ctcase_suite_id' => $this->id, |
| 108 | + 'ctcase_name' => $caseName, |
| 109 | + ), |
| 110 | + __METHOD__ ); |
| 111 | + $id = intval( $dbw->insertId() ); |
| 112 | + $this->mCaseMap[$caseName] = $id; |
| 113 | + return $id; |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + protected function loadCaseMap() { |
| 118 | + if( is_null( $this->mCaseMap ) ) { |
| 119 | + $this->mCaseMap = array(); |
| 120 | + $dbw = wfGetDB( DB_MASTER ); |
| 121 | + $result = $dbw->select( 'code_test_case', |
| 122 | + array( |
| 123 | + 'ctcase_id', |
| 124 | + 'ctcase_name', |
| 125 | + ), |
| 126 | + array( |
| 127 | + 'ctcase_suite_id' => $this->id, |
| 128 | + ), |
| 129 | + __METHOD__ |
| 130 | + ); |
| 131 | + foreach( $result as $row ) { |
| 132 | + $this->mCaseMap[$row->ctcase_name] = intval( $row->ctcase_id ); |
| 133 | + } |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + public function saveResults( $results ) { |
| 138 | + $this->insertResults( $results ); |
| 139 | + $this->status = "complete"; |
| 140 | + $dbw = wfGetDB( DB_MASTER ); |
| 141 | + $dbw->update( |
| 142 | + 'code_test_run', |
| 143 | + array( |
| 144 | + 'ctrun_status' => $this->status, |
| 145 | + 'ctrun_count_total' => $this->countTotal, |
| 146 | + 'ctrun_count_success' => $this->countSuccess, |
| 147 | + ), |
| 148 | + array( |
| 149 | + 'ctrun_id' => $this->id, |
| 150 | + ), |
| 151 | + __METHOD__ ); |
| 152 | + } |
| 153 | + |
| 154 | + public function insertResults( $results ) { |
| 155 | + $dbw = wfGetDB( DB_MASTER ); |
| 156 | + $this->countTotal = 0; |
| 157 | + $this->countSuccess = 0; |
| 158 | + foreach( $results as $caseName => $result ) { |
| 159 | + $this->countTotal++; |
| 160 | + if( $result ) { |
| 161 | + $this->countSuccess++; |
| 162 | + } |
| 163 | + $insertData[] = array( |
| 164 | + 'ctresult_run_id' => $this->id, |
| 165 | + 'ctresult_case_id' => $this->getCaseId( $caseName ), |
| 166 | + 'ctresult_success' => $result ? 1 : 0, |
| 167 | + ); |
| 168 | + } |
| 169 | + $dbw->insert( 'code_test_result', $insertData, __METHOD__ ); |
| 170 | + } |
| 171 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/CodeTestRun.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 172 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/backend/Subversion.php |
— | — | @@ -0,0 +1,327 @@ |
| 2 | +<?php |
| 3 | +if ( !defined( 'MEDIAWIKI' ) ) die(); |
| 4 | + |
| 5 | +abstract class SubversionAdaptor { |
| 6 | + protected $mRepo; |
| 7 | + |
| 8 | + public static function newFromRepo( $repo ) { |
| 9 | + global $wgSubversionProxy, $wgSubversionProxyTimeout; |
| 10 | + if ( $wgSubversionProxy ) { |
| 11 | + return new SubversionProxy( $repo, $wgSubversionProxy, $wgSubversionProxyTimeout ); |
| 12 | + } elseif ( function_exists( 'svn_log' ) ) { |
| 13 | + return new SubversionPecl( $repo ); |
| 14 | + } else { |
| 15 | + return new SubversionShell( $repo ); |
| 16 | + } |
| 17 | + } |
| 18 | + |
| 19 | + function __construct( $repo ) { |
| 20 | + $this->mRepo = $repo; |
| 21 | + } |
| 22 | + |
| 23 | + abstract function getFile( $path, $rev = null ); |
| 24 | + |
| 25 | + abstract function getDiff( $path, $rev1, $rev2 ); |
| 26 | + |
| 27 | + abstract function getDirList( $path, $rev = null ); |
| 28 | + |
| 29 | + /* |
| 30 | + array of array( |
| 31 | + 'rev' => 123, |
| 32 | + 'author' => 'myname', |
| 33 | + 'msg' => 'log message' |
| 34 | + 'date' => '8601 date', |
| 35 | + 'paths' => array( |
| 36 | + array( |
| 37 | + 'action' => one of M, A, D, R |
| 38 | + 'path' => repo URL of file, |
| 39 | + ), |
| 40 | + ... |
| 41 | + ) |
| 42 | + */ |
| 43 | + abstract function getLog( $path, $startRev = null, $endRev = null ); |
| 44 | + |
| 45 | + protected function _rev( $rev, $default ) { |
| 46 | + if ( $rev === null ) { |
| 47 | + return $default; |
| 48 | + } else { |
| 49 | + return intval( $rev ); |
| 50 | + } |
| 51 | + } |
| 52 | +} |
| 53 | + |
| 54 | +/** |
| 55 | + * Using the SVN PECL extension... |
| 56 | + */ |
| 57 | +class SubversionPecl extends SubversionAdaptor { |
| 58 | + function getFile( $path, $rev = null ) { |
| 59 | + return svn_cat( $this->mRepo . $path, $rev ); |
| 60 | + } |
| 61 | + |
| 62 | + function getDiff( $path, $rev1, $rev2 ) { |
| 63 | + list( $fout, $ferr ) = svn_diff( |
| 64 | + $this->mRepo . $path, $rev1, |
| 65 | + $this->mRepo . $path, $rev2 ); |
| 66 | + |
| 67 | + if ( $fout ) { |
| 68 | + // We have to read out the file descriptors. :P |
| 69 | + $out = ''; |
| 70 | + while ( !feof( $fout ) ) { |
| 71 | + $out .= fgets( $fout ); |
| 72 | + } |
| 73 | + fclose( $fout ); |
| 74 | + fclose( $ferr ); |
| 75 | + |
| 76 | + return $out; |
| 77 | + } else { |
| 78 | + return new MWException( "Diffing error" ); |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + function getDirList( $path, $rev = null ) { |
| 83 | + return svn_ls( $this->mRepo . $path, |
| 84 | + $this->_rev( $rev, SVN_REVISION_HEAD ) ); |
| 85 | + } |
| 86 | + |
| 87 | + function getLog( $path, $startRev = null, $endRev = null ) { |
| 88 | + return svn_log( $this->mRepo . $path, |
| 89 | + $this->_rev( $startRev, SVN_REVISION_INITIAL ), |
| 90 | + $this->_rev( $endRev, SVN_REVISION_HEAD ) ); |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +/** |
| 95 | + * Using the thingy-bobber |
| 96 | + */ |
| 97 | +class SubversionShell extends SubversionAdaptor { |
| 98 | + function getFile( $path, $rev = null ) { |
| 99 | + if ( $rev ) |
| 100 | + $path .= "@$rev"; |
| 101 | + $command = sprintf( |
| 102 | + "svn cat %s %s", |
| 103 | + $this->getExtraArgs(), |
| 104 | + wfEscapeShellArg( $this->mRepo . $path ) ); |
| 105 | + |
| 106 | + return wfShellExec( $command ); |
| 107 | + } |
| 108 | + |
| 109 | + function getDiff( $path, $rev1, $rev2 ) { |
| 110 | + $command = sprintf( |
| 111 | + "svn diff -r%d:%d %s %s", |
| 112 | + intval( $rev1 ), |
| 113 | + intval( $rev2 ), |
| 114 | + $this->getExtraArgs(), |
| 115 | + wfEscapeShellArg( $this->mRepo . $path ) ); |
| 116 | + |
| 117 | + return wfShellExec( $command ); |
| 118 | + } |
| 119 | + |
| 120 | + function getLog( $path, $startRev = null, $endRev = null ) { |
| 121 | + $lang = wfIsWindows() ? "" : "LC_ALL=en_US.utf-8 "; |
| 122 | + $command = sprintf( |
| 123 | + "{$lang}svn log -v -r%s:%s %s %s", |
| 124 | + wfEscapeShellArg( $this->_rev( $startRev, 'BASE' ) ), |
| 125 | + wfEscapeShellArg( $this->_rev( $endRev, 'HEAD' ) ), |
| 126 | + $this->getExtraArgs(), |
| 127 | + wfEscapeShellArg( $this->mRepo . $path ) ); |
| 128 | + |
| 129 | + $lines = explode( "\n", wfShellExec( $command ) ); |
| 130 | + $out = array(); |
| 131 | + |
| 132 | + $divider = str_repeat( '-', 72 ); |
| 133 | + $formats = array( |
| 134 | + 'rev' => '/^r(\d+)$/', |
| 135 | + 'author' => '/^(.*)$/', |
| 136 | + 'date' => '/^(.*?) \(.*\)$/', |
| 137 | + 'lines' => '/^(\d+) lines?$/', |
| 138 | + ); |
| 139 | + $state = "start"; |
| 140 | + foreach ( $lines as $line ) { |
| 141 | + $line = rtrim( $line ); |
| 142 | + |
| 143 | + switch( $state ) { |
| 144 | + case "start": |
| 145 | + if ( $line == $divider ) { |
| 146 | + $state = "revdata"; |
| 147 | + break; |
| 148 | + } else { |
| 149 | + return $out; |
| 150 | + # throw new MWException( "Unexpected start line: $line" ); |
| 151 | + } |
| 152 | + case "revdata": |
| 153 | + if ( $line == "" ) { |
| 154 | + $state = "done"; |
| 155 | + break; |
| 156 | + } |
| 157 | + $data = array(); |
| 158 | + $bits = explode( " | ", $line ); |
| 159 | + $i = 0; |
| 160 | + foreach ( $formats as $key => $regex ) { |
| 161 | + $text = $bits[$i++]; |
| 162 | + if ( preg_match( $regex, $text, $matches ) ) { |
| 163 | + $data[$key] = $matches[1]; |
| 164 | + } else { |
| 165 | + throw new MWException( |
| 166 | + "Unexpected format for $key in '$text'" ); |
| 167 | + } |
| 168 | + } |
| 169 | + $data['msg'] = ''; |
| 170 | + $data['paths'] = array(); |
| 171 | + $state = 'changedpaths'; |
| 172 | + break; |
| 173 | + case "changedpaths": |
| 174 | + if ( $line == "Changed paths:" ) { // broken when svn messages are not in English |
| 175 | + $state = "path"; |
| 176 | + } elseif ( $line == "" ) { |
| 177 | + // No changed paths? |
| 178 | + $state = "msg"; |
| 179 | + } else { |
| 180 | + throw new MWException( |
| 181 | + "Expected 'Changed paths:' or '', got '$line'" ); |
| 182 | + } |
| 183 | + break; |
| 184 | + case "path": |
| 185 | + if ( $line == "" ) { |
| 186 | + // Out of paths. Move on to the message... |
| 187 | + $state = 'msg'; |
| 188 | + } else { |
| 189 | + if ( preg_match( '/^ (.) (.*)$/', $line, $matches ) ) { |
| 190 | + $data['paths'][] = array( |
| 191 | + 'action' => $matches[1], |
| 192 | + 'path' => $matches[2] ); |
| 193 | + } |
| 194 | + } |
| 195 | + break; |
| 196 | + case "msg": |
| 197 | + $data['msg'] .= $line; |
| 198 | + if ( --$data['lines'] ) { |
| 199 | + $data['msg'] .= "\n"; |
| 200 | + } else { |
| 201 | + unset( $data['lines'] ); |
| 202 | + $out[] = $data; |
| 203 | + $state = "start"; |
| 204 | + } |
| 205 | + break; |
| 206 | + case "done": |
| 207 | + throw new MWException( "Unexpected input after end: $line" ); |
| 208 | + default: |
| 209 | + throw new MWException( "Invalid state '$state'" ); |
| 210 | + } |
| 211 | + } |
| 212 | + |
| 213 | + return $out; |
| 214 | + } |
| 215 | + |
| 216 | + function getDirList( $path, $rev = null ) { |
| 217 | + $command = sprintf( |
| 218 | + "svn list --xml -r%s %s %s", |
| 219 | + wfEscapeShellArg( $this->_rev( $rev, 'HEAD' ) ), |
| 220 | + $this->getExtraArgs(), |
| 221 | + wfEscapeShellArg( $this->mRepo . $path ) ); |
| 222 | + $document = new DOMDocument(); |
| 223 | + |
| 224 | + if ( !@$document->loadXML( wfShellExec( $command ) ) ) |
| 225 | + // svn list --xml returns invalid XML if the file does not exist |
| 226 | + // FIXME: report bug upstream |
| 227 | + return false; |
| 228 | + |
| 229 | + $entries = $document->getElementsByTagName( 'entry' ); |
| 230 | + $result = array(); |
| 231 | + foreach ( $entries as $entry ) { |
| 232 | + $item = array(); |
| 233 | + $item['type'] = $entry->getAttribute( 'kind' ); |
| 234 | + foreach ( $entry->childNodes as $child ) { |
| 235 | + switch ( $child->nodeName ) { |
| 236 | + case 'name': |
| 237 | + $item['name'] = $child->textContent; |
| 238 | + break; |
| 239 | + case 'size': |
| 240 | + $item['size'] = intval( $child->textContent ); |
| 241 | + break; |
| 242 | + case 'commit': |
| 243 | + $item['created_rev'] = intval( $child->getAttribute( 'revision' ) ); |
| 244 | + foreach ( $child->childNodes as $commitEntry ) { |
| 245 | + switch ( $commitEntry->nodeName ) { |
| 246 | + case 'author': |
| 247 | + $item['last_author'] = $commitEntry->textContent; |
| 248 | + break; |
| 249 | + case 'date': |
| 250 | + $item['time_t'] = wfTimestamp( TS_UNIX, $commitEntry->textContent ); |
| 251 | + break; |
| 252 | + } |
| 253 | + } |
| 254 | + break; |
| 255 | + } |
| 256 | + } |
| 257 | + $result[] = $item; |
| 258 | + } |
| 259 | + return $result; |
| 260 | + } |
| 261 | + |
| 262 | + /** |
| 263 | + * Returns a string of extra arguments to be passed into the shell commands |
| 264 | + */ |
| 265 | + private function getExtraArgs() { |
| 266 | + global $wgSubversionOptions, $wgSubversionUser, $wgSubversionPassword; |
| 267 | + $args = $wgSubversionOptions; |
| 268 | + if ( $wgSubversionUser ) { |
| 269 | + $args .= ' --username ' . wfEscapeShellArg( $wgSubversionUser ) |
| 270 | + . ' --password ' . wfEscapeShellArg( $wgSubversionPassword ); |
| 271 | + } |
| 272 | + return $args; |
| 273 | + } |
| 274 | +} |
| 275 | + |
| 276 | +/** |
| 277 | + * Using a remote JSON proxy |
| 278 | + */ |
| 279 | +class SubversionProxy extends SubversionAdaptor { |
| 280 | + function __construct( $repo, $proxy, $timeout = 30 ) { |
| 281 | + parent::__construct( $repo ); |
| 282 | + $this->mProxy = $proxy; |
| 283 | + $this->mTimeout = $timeout; |
| 284 | + } |
| 285 | + |
| 286 | + function getFile( $path, $rev = null ) { |
| 287 | + throw new MWException( "NYI" ); |
| 288 | + } |
| 289 | + |
| 290 | + function getDiff( $path, $rev1, $rev2 ) { |
| 291 | + return $this->_proxy( array( |
| 292 | + 'action' => 'diff', |
| 293 | + 'path' => $path, |
| 294 | + 'rev1' => $rev1, |
| 295 | + 'rev2' => $rev2 ) ); |
| 296 | + } |
| 297 | + |
| 298 | + function getLog( $path, $startRev = null, $endRev = null ) { |
| 299 | + return $this->_proxy( array( |
| 300 | + 'action' => 'log', |
| 301 | + 'path' => $path, |
| 302 | + 'start' => $startRev, |
| 303 | + 'end' => $endRev ) ); |
| 304 | + } |
| 305 | + |
| 306 | + function getDirList( $path, $rev = null ) { |
| 307 | + return $this->_proxy( array( |
| 308 | + 'action' => 'list', |
| 309 | + 'path' => $path, |
| 310 | + 'rev' => $rev ) ); |
| 311 | + } |
| 312 | + |
| 313 | + protected function _proxy( $params ) { |
| 314 | + foreach ( $params as $key => $val ) { |
| 315 | + if ( is_null( $val ) ) { |
| 316 | + // Don't pass nulls to remote |
| 317 | + unset( $params[$key] ); |
| 318 | + } |
| 319 | + } |
| 320 | + $target = $this->mProxy . '?' . wfArrayToCgi( $params ); |
| 321 | + $blob = Http::get( $target, $this->mTimeout ); |
| 322 | + if ( $blob === false ) { |
| 323 | + throw new MWException( "SVN proxy error" ); |
| 324 | + } |
| 325 | + $data = unserialize( $blob ); |
| 326 | + return $data; |
| 327 | + } |
| 328 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/Subversion.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 329 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/backend/CodePropChange.php |
— | — | @@ -0,0 +1,24 @@ |
| 2 | +<?php |
| 3 | +if ( !defined( 'MEDIAWIKI' ) ) die(); |
| 4 | + |
| 5 | +class CodePropChange { |
| 6 | + function __construct( $rev ) { |
| 7 | + $this->rev = $rev; |
| 8 | + } |
| 9 | + |
| 10 | + static function newFromRow( $rev, $row ) { |
| 11 | + return self::newFromData( $rev, get_object_vars( $row ) ); |
| 12 | + } |
| 13 | + |
| 14 | + static function newFromData( $rev, $data ) { |
| 15 | + $change = new CodeComment( $rev ); |
| 16 | + $change->attrib = $data['cpc_attrib']; |
| 17 | + $change->removed = $data['cpc_removed']; |
| 18 | + $change->added = $data['cpc_added']; |
| 19 | + $change->user = $data['cpc_user']; |
| 20 | + // We'd prefer the up to date user table name |
| 21 | + $change->userText = isset( $data['user_name'] ) ? $data['user_name'] : $data['cpc_user_text']; |
| 22 | + $change->timestamp = wfTimestamp( TS_MW, $data['cpc_timestamp'] ); |
| 23 | + return $change; |
| 24 | + } |
| 25 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/CodePropChange.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 26 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/backend/CodeTestSuite.php |
— | — | @@ -0,0 +1,38 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class CodeTestSuite { |
| 5 | + public static function newFromRow( CodeRepository $repo, $row ) { |
| 6 | + $suite = new CodeTestSuite(); |
| 7 | + $suite->id = intval( $row->ctsuite_id ); |
| 8 | + $suite->repo = $repo; |
| 9 | + $suite->repoId = $repo->getId(); |
| 10 | + $suite->branchPath = $row->ctsuite_branch_path; |
| 11 | + $suite->name = $row->ctsuite_name; |
| 12 | + $suite->desc = $row->ctsuite_desc; |
| 13 | + return $suite; |
| 14 | + } |
| 15 | + |
| 16 | + function getRun( $revId ) { |
| 17 | + return CodeTestRun::newFromRevId( $this, $revId ); |
| 18 | + } |
| 19 | + |
| 20 | + function setStatus( $revId, $status ) { |
| 21 | + $run = $this->getRun( $revId ); |
| 22 | + if( $run ) { |
| 23 | + $run->setStatus( $status ); |
| 24 | + } else { |
| 25 | + $run = CodeTestRun::insertRun( $this, $revId, $status ); |
| 26 | + } |
| 27 | + return $run; |
| 28 | + } |
| 29 | + |
| 30 | + function saveResults( $revId, $results ) { |
| 31 | + $run = $this->getRun( $revId ); |
| 32 | + if( $run ) { |
| 33 | + $run->saveResults( $results ); |
| 34 | + } else { |
| 35 | + $run = CodeTestRun::insertRun( $this, $revId, "complete", $results ); |
| 36 | + } |
| 37 | + return $run; |
| 38 | + } |
| 39 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/CodeTestSuite.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 40 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/backend/DiffHighlighter.php |
— | — | @@ -0,0 +1,64 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Highlight a SVN diff for easier readibility |
| 6 | + */ |
| 7 | +class CodeDiffHighlighter { |
| 8 | + |
| 9 | + /** |
| 10 | + * Main entry point. Given a diff text, highlight it |
| 11 | + * and wrap it in a div |
| 12 | + * @param $text string Text to highlight |
| 13 | + * @return string |
| 14 | + */ |
| 15 | + function render( $text ) { |
| 16 | + return '<pre class="mw-codereview-diff">' . |
| 17 | + $this->splitLines( $text ) . |
| 18 | + "</pre>\n"; |
| 19 | + } |
| 20 | + |
| 21 | + /** |
| 22 | + * Given a bunch of text, split it into individual |
| 23 | + * lines, color them, then put it back into one big |
| 24 | + * string |
| 25 | + * @param $text string Text to split and highlight |
| 26 | + * @return string |
| 27 | + */ |
| 28 | + function splitLines( $text ) { |
| 29 | + return implode( "\n", |
| 30 | + array_map( array( $this, 'colorLine' ), |
| 31 | + explode( "\n", $text ) ) ); |
| 32 | + } |
| 33 | + |
| 34 | + /** |
| 35 | + * Turn a diff line into a properly formatted string suitable |
| 36 | + * for output |
| 37 | + * @param $line string Line from a diff |
| 38 | + * @return string |
| 39 | + */ |
| 40 | + function colorLine( $line ) { |
| 41 | + list( $element, $attribs ) = $this->tagForLine( $line ); |
| 42 | + return Xml::element( $element, $attribs, $line ); |
| 43 | + } |
| 44 | + |
| 45 | + /** |
| 46 | + * Take a line of a diff and apply the appropriate stylings |
| 47 | + * @param $line string Line to check |
| 48 | + * @return array |
| 49 | + */ |
| 50 | + function tagForLine( $line ) { |
| 51 | + static $default = array( 'span', array() ); |
| 52 | + static $tags = array( |
| 53 | + '-' => array( 'del', array() ), |
| 54 | + '+' => array( 'ins', array() ), |
| 55 | + '@' => array( 'span', array( 'class' => 'meta' ) ), |
| 56 | + ' ' => array( 'span', array() ), |
| 57 | + ); |
| 58 | + $first = substr( $line, 0, 1 ); |
| 59 | + if ( isset( $tags[$first] ) ) |
| 60 | + return $tags[$first]; |
| 61 | + else |
| 62 | + return $default; |
| 63 | + } |
| 64 | + |
| 65 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/DiffHighlighter.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 66 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/backend/CodeRevision.php |
— | — | @@ -0,0 +1,681 @@ |
| 2 | +<?php |
| 3 | +if ( !defined( 'MEDIAWIKI' ) ) die(); |
| 4 | + |
| 5 | +class CodeRevision { |
| 6 | + public static function newFromSvn( CodeRepository $repo, $data ) { |
| 7 | + $rev = new CodeRevision(); |
| 8 | + $rev->mRepoId = $repo->getId(); |
| 9 | + $rev->mRepo = $repo; |
| 10 | + $rev->mId = intval( $data['rev'] ); |
| 11 | + $rev->mAuthor = $data['author']; |
| 12 | + $rev->mTimestamp = wfTimestamp( TS_MW, strtotime( $data['date'] ) ); |
| 13 | + $rev->mMessage = rtrim( $data['msg'] ); |
| 14 | + $rev->mPaths = $data['paths']; |
| 15 | + $rev->mStatus = 'new'; |
| 16 | + |
| 17 | + $common = null; |
| 18 | + if ( $rev->mPaths ) { |
| 19 | + if ( count( $rev->mPaths ) == 1 ) |
| 20 | + $common = $rev->mPaths[0]['path']; |
| 21 | + else { |
| 22 | + $first = array_shift( $rev->mPaths ); |
| 23 | + |
| 24 | + $common = explode( '/', $first['path'] ); |
| 25 | + |
| 26 | + foreach ( $rev->mPaths as $path ) { |
| 27 | + $compare = explode( '/', $path['path'] ); |
| 28 | + |
| 29 | + // make sure $common is the shortest path |
| 30 | + if ( count( $compare ) < count( $common ) ) |
| 31 | + list( $compare, $common ) = array( $common, $compare ); |
| 32 | + |
| 33 | + $tmp = array(); |
| 34 | + foreach ( $common as $k => $v ) |
| 35 | + if ( $v == $compare[$k] ) $tmp[] = $v; |
| 36 | + else break; |
| 37 | + $common = $tmp; |
| 38 | + } |
| 39 | + $common = implode( '/', $common ); |
| 40 | + |
| 41 | + array_unshift( $rev->mPaths, $first ); |
| 42 | + } |
| 43 | + } |
| 44 | + $rev->mCommonPath = $common; |
| 45 | + return $rev; |
| 46 | + } |
| 47 | + |
| 48 | + public static function newFromRow( CodeRepository $repo, $row ) { |
| 49 | + $rev = new CodeRevision(); |
| 50 | + $rev->mRepoId = intval( $row->cr_repo_id ); |
| 51 | + if ( $rev->mRepoId != $repo->getId() ) { |
| 52 | + throw new MWException( "Invalid repo ID in " . __METHOD__ ); |
| 53 | + } |
| 54 | + $rev->mRepo = $repo; |
| 55 | + $rev->mId = intval( $row->cr_id ); |
| 56 | + $rev->mAuthor = $row->cr_author; |
| 57 | + $rev->mTimestamp = wfTimestamp( TS_MW, $row->cr_timestamp ); |
| 58 | + $rev->mMessage = $row->cr_message; |
| 59 | + $rev->mStatus = $row->cr_status; |
| 60 | + $rev->mCommonPath = $row->cr_path; |
| 61 | + return $rev; |
| 62 | + } |
| 63 | + |
| 64 | + public function getId() { |
| 65 | + return intval( $this->mId ); |
| 66 | + } |
| 67 | + |
| 68 | + public function getRepoId() { |
| 69 | + return intval( $this->mRepoId ); |
| 70 | + } |
| 71 | + |
| 72 | + public function getAuthor() { |
| 73 | + return $this->mAuthor; |
| 74 | + } |
| 75 | + |
| 76 | + public function getWikiUser() { |
| 77 | + return $this->mRepo->authorWikiUser( $this->getAuthor() ); |
| 78 | + } |
| 79 | + |
| 80 | + public function getTimestamp() { |
| 81 | + return $this->mTimestamp; |
| 82 | + } |
| 83 | + |
| 84 | + public function getMessage() { |
| 85 | + return $this->mMessage; |
| 86 | + } |
| 87 | + |
| 88 | + public function getStatus() { |
| 89 | + return $this->mStatus; |
| 90 | + } |
| 91 | + |
| 92 | + public function getCommonPath() { |
| 93 | + return $this->mCommonPath; |
| 94 | + } |
| 95 | + |
| 96 | + public static function getPossibleStates() { |
| 97 | + return array( 'new', 'fixme', 'reverted', 'resolved', 'ok', 'verified', 'deferred' ); |
| 98 | + } |
| 99 | + |
| 100 | + public function isValidStatus( $status ) { |
| 101 | + return in_array( $status, self::getPossibleStates(), true ); |
| 102 | + } |
| 103 | + |
| 104 | + public function setStatus( $status, $user ) { |
| 105 | + if ( !$this->isValidStatus( $status ) ) { |
| 106 | + throw new MWException( "Tried to save invalid code revision status" ); |
| 107 | + } |
| 108 | + // Get the old status from the master |
| 109 | + $dbw = wfGetDB( DB_MASTER ); |
| 110 | + $oldStatus = $dbw->selectField( 'code_rev', |
| 111 | + 'cr_status', |
| 112 | + array( 'cr_repo_id' => $this->mRepoId, 'cr_id' => $this->mId ), |
| 113 | + __METHOD__ |
| 114 | + ); |
| 115 | + if ( $oldStatus === $status ) { |
| 116 | + return false; // nothing to do here |
| 117 | + } |
| 118 | + // Update status |
| 119 | + $this->mStatus = $status; |
| 120 | + $dbw->update( 'code_rev', |
| 121 | + array( 'cr_status' => $status ), |
| 122 | + array( |
| 123 | + 'cr_repo_id' => $this->mRepoId, |
| 124 | + 'cr_id' => $this->mId ), |
| 125 | + __METHOD__ |
| 126 | + ); |
| 127 | + // Log this change |
| 128 | + if ( $user && $user->getId() ) { |
| 129 | + $dbw->insert( 'code_prop_changes', |
| 130 | + array( |
| 131 | + 'cpc_repo_id' => $this->getRepoId(), |
| 132 | + 'cpc_rev_id' => $this->getId(), |
| 133 | + 'cpc_attrib' => 'status', |
| 134 | + 'cpc_removed' => $oldStatus, |
| 135 | + 'cpc_added' => $status, |
| 136 | + 'cpc_timestamp' => $dbw->timestamp(), |
| 137 | + 'cpc_user' => $user->getId(), |
| 138 | + 'cpc_user_text' => $user->getName() |
| 139 | + ), |
| 140 | + __METHOD__ |
| 141 | + ); |
| 142 | + } |
| 143 | + return true; |
| 144 | + } |
| 145 | + |
| 146 | + /** |
| 147 | + * Quickie protection against huuuuuuuuge batch inserts |
| 148 | + */ |
| 149 | + protected function insertChunks( $db, $table, $data, $method, $options=array() ) { |
| 150 | + $chunkSize = 100; |
| 151 | + for( $i = 0; $i < count( $data ); $i += $chunkSize ) { |
| 152 | + $db->insert( 'code_paths', |
| 153 | + array_slice( $data, $i, $chunkSize ), |
| 154 | + __METHOD__, |
| 155 | + array( 'IGNORE' ) ); |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + public function save() { |
| 160 | + $dbw = wfGetDB( DB_MASTER ); |
| 161 | + $dbw->begin(); |
| 162 | + |
| 163 | + $dbw->insert( 'code_rev', |
| 164 | + array( |
| 165 | + 'cr_repo_id' => $this->mRepoId, |
| 166 | + 'cr_id' => $this->mId, |
| 167 | + 'cr_author' => $this->mAuthor, |
| 168 | + 'cr_timestamp' => $dbw->timestamp( $this->mTimestamp ), |
| 169 | + 'cr_message' => $this->mMessage, |
| 170 | + 'cr_status' => $this->mStatus, |
| 171 | + 'cr_path' => $this->mCommonPath ), |
| 172 | + __METHOD__, |
| 173 | + array( 'IGNORE' ) |
| 174 | + ); |
| 175 | + // Already exists? Update the row! |
| 176 | + $newRevision = $dbw->affectedRows() > 0; |
| 177 | + if ( !$newRevision ) { |
| 178 | + $dbw->update( 'code_rev', |
| 179 | + array( |
| 180 | + 'cr_author' => $this->mAuthor, |
| 181 | + 'cr_timestamp' => $dbw->timestamp( $this->mTimestamp ), |
| 182 | + 'cr_message' => $this->mMessage, |
| 183 | + 'cr_path' => $this->mCommonPath ), |
| 184 | + array( |
| 185 | + 'cr_repo_id' => $this->mRepoId, |
| 186 | + 'cr_id' => $this->mId ), |
| 187 | + __METHOD__ |
| 188 | + ); |
| 189 | + } |
| 190 | + // Update path tracking used for output and searching |
| 191 | + if ( $this->mPaths ) { |
| 192 | + $data = array(); |
| 193 | + foreach ( $this->mPaths as $path ) { |
| 194 | + $data[] = array( |
| 195 | + 'cp_repo_id' => $this->mRepoId, |
| 196 | + 'cp_rev_id' => $this->mId, |
| 197 | + 'cp_path' => $path['path'], |
| 198 | + 'cp_action' => $path['action'] ); |
| 199 | + } |
| 200 | + $this->insertChunks( $dbw, 'code_paths', $data, __METHOD__, array( 'IGNORE' ) ); |
| 201 | + } |
| 202 | + // Update bug references table... |
| 203 | + $affectedBugs = array(); |
| 204 | + if ( preg_match_all( '/\bbug (\d+)\b/', $this->mMessage, $m ) ) { |
| 205 | + $data = array(); |
| 206 | + foreach( $m[1] as $bug ) { |
| 207 | + $data[] = array( |
| 208 | + 'cb_repo_id' => $this->mRepoId, |
| 209 | + 'cb_from' => $this->mId, |
| 210 | + 'cb_bug' => $bug |
| 211 | + ); |
| 212 | + $affectedBugs[] = intval($bug); |
| 213 | + } |
| 214 | + $dbw->insert( 'code_bugs', $data, __METHOD__, array( 'IGNORE' ) ); |
| 215 | + } |
| 216 | + // Get the revisions this commit references... |
| 217 | + $affectedRevs = array(); |
| 218 | + if ( preg_match_all( '/\br(\d{2,})\b/', $this->mMessage, $m ) ) { |
| 219 | + foreach( $m[1] as $rev ) { |
| 220 | + $affectedRevs[] = intval($rev); |
| 221 | + } |
| 222 | + } |
| 223 | + // Also, get previous revisions that have bugs in common... |
| 224 | + if( count($affectedBugs) ) { |
| 225 | + $res = $dbw->select( 'code_bugs', |
| 226 | + array( 'cb_from' ), |
| 227 | + array( |
| 228 | + 'cb_repo_id' => $this->mRepoId, |
| 229 | + 'cb_bug' => $affectedBugs, |
| 230 | + 'cb_from < '.intval($this->mId), # just in case |
| 231 | + ), |
| 232 | + __METHOD__, |
| 233 | + array( 'USE INDEX' => 'cb_repo_id' ) |
| 234 | + ); |
| 235 | + foreach( $res as $row ) { |
| 236 | + $affectedRevs[] = intval($row->cb_from); |
| 237 | + } |
| 238 | + } |
| 239 | + // Filter any duplicate revisions |
| 240 | + if( count($affectedRevs) ) { |
| 241 | + $data = array(); |
| 242 | + $affectedRevs = array_unique($affectedRevs); |
| 243 | + foreach( $affectedRevs as $rev ) { |
| 244 | + $data[] = array( |
| 245 | + 'cf_repo_id' => $this->mRepoId, |
| 246 | + 'cf_from' => $this->mId, |
| 247 | + 'cf_to' => $rev |
| 248 | + ); |
| 249 | + $affectedRevs[] = intval($rev); |
| 250 | + } |
| 251 | + $dbw->insert( 'code_relations', $data, __METHOD__, array( 'IGNORE' ) ); |
| 252 | + } |
| 253 | + // Email the authors of revisions that this follows up on |
| 254 | + if( $newRevision && count($affectedRevs) > 0 ) { |
| 255 | + // Get committer wiki user name, or repo name at least |
| 256 | + $user = $this->mRepo->authorWikiUser( $this->mAuthor ); |
| 257 | + $committer = $user ? $user->getName() : htmlspecialchars($this->mAuthor); |
| 258 | + // Get the authors of these revisions |
| 259 | + $res = $dbw->select( 'code_rev', |
| 260 | + array( 'cr_author', 'cr_id' ), |
| 261 | + array( |
| 262 | + 'cr_repo_id' => $this->mRepoId, |
| 263 | + 'cr_id' => $affectedRevs, |
| 264 | + 'cr_id < '.intval($this->mId), # just in case |
| 265 | + // No sense in notifying if it's the same person |
| 266 | + 'cr_author != '.$dbw->addQuotes($this->mAuthor) |
| 267 | + ), |
| 268 | + __METHOD__, |
| 269 | + array( 'USE INDEX' => 'PRIMARY' ) |
| 270 | + ); |
| 271 | + // Get repo and build comment title (for url) |
| 272 | + $title = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $this->mId ); |
| 273 | + $url = $title->getFullUrl(); |
| 274 | + wfLoadExtensionMessages( 'CodeReview' ); |
| 275 | + foreach( $res as $row ) { |
| 276 | + $user = $this->mRepo->authorWikiUser( $row->cr_author ); |
| 277 | + // User must exist on wiki and have a valid email addy |
| 278 | + if( !$user || !$user->canReceiveEmail() ) continue; |
| 279 | + // Send message in receiver's language |
| 280 | + $lang = array( 'language' => $user->getOption( 'language' ) ); |
| 281 | + $user->sendMail( |
| 282 | + wfMsgExt( 'codereview-email-subj2', $lang, $this->mRepo->getName(), $row->cr_id ), |
| 283 | + wfMsgExt( 'codereview-email-body2', $lang, $committer, $row->cr_id, $url, $this->mMessage ) |
| 284 | + ); |
| 285 | + } |
| 286 | + } |
| 287 | + $dbw->commit(); |
| 288 | + } |
| 289 | + |
| 290 | + public function getModifiedPaths() { |
| 291 | + $dbr = wfGetDB( DB_SLAVE ); |
| 292 | + return $dbr->select( |
| 293 | + 'code_paths', |
| 294 | + array( 'cp_path', 'cp_action' ), |
| 295 | + array( 'cp_repo_id' => $this->mRepoId, 'cp_rev_id' => $this->mId ), |
| 296 | + __METHOD__ |
| 297 | + ); |
| 298 | + } |
| 299 | + |
| 300 | + public function isDiffable() { |
| 301 | + $paths = $this->getModifiedPaths(); |
| 302 | + if ( !$paths->numRows() || $paths->numRows() > 20 ) { |
| 303 | + return false; // things need to get done this year |
| 304 | + } |
| 305 | + return true; |
| 306 | + } |
| 307 | + |
| 308 | + public function previewComment( $text, $review, $parent = null ) { |
| 309 | + $data = $this->commentData( $text, $review, $parent ); |
| 310 | + $data['cc_id'] = null; |
| 311 | + return CodeComment::newFromData( $this, $data ); |
| 312 | + } |
| 313 | + |
| 314 | + public function saveComment( $text, $review, $parent = null ) { |
| 315 | + global $wgUser; |
| 316 | + if ( !strlen( $text ) ) { |
| 317 | + return 0; |
| 318 | + } |
| 319 | + $dbw = wfGetDB( DB_MASTER ); |
| 320 | + $data = $this->commentData( $text, $review, $parent ); |
| 321 | + |
| 322 | + $dbw->begin(); |
| 323 | + $data['cc_id'] = $dbw->nextSequenceValue( 'code_comment_cc_id' ); |
| 324 | + $dbw->insert( 'code_comment', $data, __METHOD__ ); |
| 325 | + $commentId = $dbw->insertId(); |
| 326 | + $dbw->commit(); |
| 327 | + |
| 328 | + // Give email notices to committer and commenters |
| 329 | + global $wgCodeReviewENotif, $wgEnableEmail; |
| 330 | + if ( $wgCodeReviewENotif && $wgEnableEmail ) { |
| 331 | + // Make list of users to send emails to |
| 332 | + $users = $this->getCommentingUsers(); |
| 333 | + if ( $user = $this->getWikiUser() ) { |
| 334 | + $users[$user->getId()] = $user; |
| 335 | + } |
| 336 | + // Get repo and build comment title (for url) |
| 337 | + $title = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $this->mId ); |
| 338 | + $title->setFragment( "#c{$commentId}" ); |
| 339 | + $url = $title->getFullUrl(); |
| 340 | + foreach ( $users as $userId => $user ) { |
| 341 | + // No sense in notifying this commenter |
| 342 | + if ( $wgUser->getId() == $user->getId() ) { |
| 343 | + continue; |
| 344 | + } |
| 345 | + // Send message in receiver's language |
| 346 | + $lang = array( 'language' => $user->getOption( 'language' ) ); |
| 347 | + if ( $user->canReceiveEmail() ) { |
| 348 | + $user->sendMail( |
| 349 | + wfMsgExt( 'codereview-email-subj', $lang, $this->mRepo->getName(), $this->mId ), |
| 350 | + wfMsgExt( 'codereview-email-body', $lang, $wgUser->getName(), $url, $this->mId, $text ) |
| 351 | + ); |
| 352 | + } |
| 353 | + } |
| 354 | + } |
| 355 | + |
| 356 | + return $commentId; |
| 357 | + } |
| 358 | + |
| 359 | + protected function commentData( $text, $review, $parent = null ) { |
| 360 | + global $wgUser; |
| 361 | + $dbw = wfGetDB( DB_MASTER ); |
| 362 | + $ts = wfTimestamp( TS_MW ); |
| 363 | + $sortkey = $this->threadedSortkey( $parent, $ts ); |
| 364 | + return array( |
| 365 | + 'cc_repo_id' => $this->mRepoId, |
| 366 | + 'cc_rev_id' => $this->mId, |
| 367 | + 'cc_text' => $text, |
| 368 | + 'cc_parent' => $parent, |
| 369 | + 'cc_user' => $wgUser->getId(), |
| 370 | + 'cc_user_text' => $wgUser->getName(), |
| 371 | + 'cc_timestamp' => $dbw->timestamp( $ts ), |
| 372 | + 'cc_review' => $review, |
| 373 | + 'cc_sortkey' => $sortkey ); |
| 374 | + } |
| 375 | + |
| 376 | + protected function threadedSortKey( $parent, $ts ) { |
| 377 | + if ( $parent ) { |
| 378 | + // We construct a threaded sort key by concatenating the timestamps |
| 379 | + // of all our parent comments |
| 380 | + $dbw = wfGetDB( DB_MASTER ); |
| 381 | + $parentKey = $dbw->selectField( 'code_comment', |
| 382 | + 'cc_sortkey', |
| 383 | + array( 'cc_id' => $parent ), |
| 384 | + __METHOD__ ); |
| 385 | + if ( $parentKey ) { |
| 386 | + return $parentKey . ',' . $ts; |
| 387 | + } else { |
| 388 | + // hmmmm |
| 389 | + throw new MWException( 'Invalid parent submission' ); |
| 390 | + } |
| 391 | + } else { |
| 392 | + return $ts; |
| 393 | + } |
| 394 | + } |
| 395 | + |
| 396 | + public function getComments() { |
| 397 | + $dbr = wfGetDB( DB_SLAVE ); |
| 398 | + $result = $dbr->select( 'code_comment', |
| 399 | + array( |
| 400 | + 'cc_id', |
| 401 | + 'cc_text', |
| 402 | + 'cc_parent', |
| 403 | + 'cc_user', |
| 404 | + 'cc_user_text', |
| 405 | + 'cc_timestamp', |
| 406 | + 'cc_review', |
| 407 | + 'cc_sortkey' ), |
| 408 | + array( |
| 409 | + 'cc_repo_id' => $this->mRepoId, |
| 410 | + 'cc_rev_id' => $this->mId ), |
| 411 | + __METHOD__, |
| 412 | + array( |
| 413 | + 'ORDER BY' => 'cc_sortkey' ) |
| 414 | + ); |
| 415 | + $comments = array(); |
| 416 | + foreach ( $result as $row ) { |
| 417 | + $comments[] = CodeComment::newFromRow( $this, $row ); |
| 418 | + } |
| 419 | + $result->free(); |
| 420 | + return $comments; |
| 421 | + } |
| 422 | + |
| 423 | + public function getPropChanges() { |
| 424 | + $dbr = wfGetDB( DB_SLAVE ); |
| 425 | + $result = $dbr->select( array( 'code_prop_changes', 'user' ), |
| 426 | + array( |
| 427 | + 'cpc_attrib', |
| 428 | + 'cpc_removed', |
| 429 | + 'cpc_added', |
| 430 | + 'cpc_timestamp', |
| 431 | + 'cpc_user', |
| 432 | + 'cpc_user_text', |
| 433 | + 'user_name' |
| 434 | + ), array( |
| 435 | + 'cpc_repo_id' => $this->mRepoId, |
| 436 | + 'cpc_rev_id' => $this->mId, |
| 437 | + ), |
| 438 | + __METHOD__, |
| 439 | + array( 'ORDER BY' => 'cpc_timestamp DESC' ), |
| 440 | + array( 'user' => array( 'LEFT JOIN', 'cpc_user = user_id' ) ) |
| 441 | + ); |
| 442 | + $changes = array(); |
| 443 | + foreach ( $result as $row ) { |
| 444 | + $changes[] = CodePropChange::newFromRow( $this, $row ); |
| 445 | + } |
| 446 | + $result->free(); |
| 447 | + return $changes; |
| 448 | + } |
| 449 | + |
| 450 | + protected function getCommentingUsers() { |
| 451 | + $dbr = wfGetDB( DB_SLAVE ); |
| 452 | + $res = $dbr->select( 'code_comment', |
| 453 | + 'DISTINCT(cc_user)', |
| 454 | + array( |
| 455 | + 'cc_repo_id' => $this->mRepoId, |
| 456 | + 'cc_rev_id' => $this->mId, |
| 457 | + 'cc_user != 0' // users only |
| 458 | + ), |
| 459 | + __METHOD__ |
| 460 | + ); |
| 461 | + $users = array(); |
| 462 | + while ( $row = $res->fetchObject() ) { |
| 463 | + $users[$row->cc_user] = User::newFromId( $row->cc_user ); |
| 464 | + } |
| 465 | + return $users; |
| 466 | + } |
| 467 | + |
| 468 | + public function getReferences() { |
| 469 | + $refs = array(); |
| 470 | + $dbr = wfGetDB( DB_SLAVE ); |
| 471 | + $res = $dbr->select( |
| 472 | + array( 'code_relations', 'code_rev' ), |
| 473 | + array( 'cr_id', 'cr_status', 'cr_timestamp', 'cr_author', 'cr_message' ), |
| 474 | + array( |
| 475 | + 'cf_repo_id' => $this->mRepoId, |
| 476 | + 'cf_to' => $this->mId, |
| 477 | + 'cr_repo_id = cf_repo_id', |
| 478 | + 'cr_id = cf_from' |
| 479 | + ), |
| 480 | + __METHOD__ |
| 481 | + ); |
| 482 | + while ( $row = $res->fetchObject() ) { |
| 483 | + $refs[] = $row; |
| 484 | + } |
| 485 | + return $refs; |
| 486 | + } |
| 487 | + |
| 488 | + public function getTags( $from = DB_SLAVE ) { |
| 489 | + $db = wfGetDB( $from ); |
| 490 | + $result = $db->select( 'code_tags', |
| 491 | + array( 'ct_tag' ), |
| 492 | + array( |
| 493 | + 'ct_repo_id' => $this->mRepoId, |
| 494 | + 'ct_rev_id' => $this->mId ), |
| 495 | + __METHOD__ ); |
| 496 | + |
| 497 | + $tags = array(); |
| 498 | + foreach ( $result as $row ) { |
| 499 | + $tags[] = $row->ct_tag; |
| 500 | + } |
| 501 | + return $tags; |
| 502 | + } |
| 503 | + |
| 504 | + public function changeTags( $addTags, $removeTags, $user = NULL ) { |
| 505 | + // Get the current tags and see what changes |
| 506 | + $tagsNow = $this->getTags( DB_MASTER ); |
| 507 | + // Normalize our input tags |
| 508 | + $addTags = $this->normalizeTags( $addTags ); |
| 509 | + $removeTags = $this->normalizeTags( $removeTags ); |
| 510 | + $addTags = array_diff( $addTags, $tagsNow ); |
| 511 | + $removeTags = array_intersect( $removeTags, $tagsNow ); |
| 512 | + // Do the queries |
| 513 | + $dbw = wfGetDB( DB_MASTER ); |
| 514 | + if ( $addTags ) { |
| 515 | + $dbw->insert( 'code_tags', |
| 516 | + $this->tagData( $addTags ), |
| 517 | + __METHOD__, |
| 518 | + array( 'IGNORE' ) |
| 519 | + ); |
| 520 | + } |
| 521 | + if ( $removeTags ) { |
| 522 | + $dbw->delete( 'code_tags', |
| 523 | + array( |
| 524 | + 'ct_repo_id' => $this->mRepoId, |
| 525 | + 'ct_rev_id' => $this->mId, |
| 526 | + 'ct_tag' => $removeTags ), |
| 527 | + __METHOD__ |
| 528 | + ); |
| 529 | + } |
| 530 | + // Log this change |
| 531 | + if ( ( $removeTags || $addTags ) && $user && $user->getId() ) { |
| 532 | + $dbw->insert( 'code_prop_changes', |
| 533 | + array( |
| 534 | + 'cpc_repo_id' => $this->getRepoId(), |
| 535 | + 'cpc_rev_id' => $this->getId(), |
| 536 | + 'cpc_attrib' => 'tags', |
| 537 | + 'cpc_removed' => implode( ',', $removeTags ), |
| 538 | + 'cpc_added' => implode( ',', $addTags ), |
| 539 | + 'cpc_timestamp' => $dbw->timestamp(), |
| 540 | + 'cpc_user' => $user->getId(), |
| 541 | + 'cpc_user_text' => $user->getName() |
| 542 | + ), |
| 543 | + __METHOD__ |
| 544 | + ); |
| 545 | + } |
| 546 | + } |
| 547 | + |
| 548 | + protected function normalizeTags( $tags ) { |
| 549 | + $out = array(); |
| 550 | + foreach ( $tags as $tag ) { |
| 551 | + $out[] = $this->normalizeTag( $tag ); |
| 552 | + } |
| 553 | + return $out; |
| 554 | + } |
| 555 | + |
| 556 | + protected function tagData( $tags ) { |
| 557 | + $data = array(); |
| 558 | + foreach ( $tags as $tag ) { |
| 559 | + $data[] = array( |
| 560 | + 'ct_repo_id' => $this->mRepoId, |
| 561 | + 'ct_rev_id' => $this->mId, |
| 562 | + 'ct_tag' => $this->normalizeTag( $tag ) ); |
| 563 | + } |
| 564 | + return $data; |
| 565 | + } |
| 566 | + |
| 567 | + public function normalizeTag( $tag ) { |
| 568 | + global $wgContLang; |
| 569 | + $lower = $wgContLang->lc( $tag ); |
| 570 | + |
| 571 | + $title = Title::newFromText( $tag ); |
| 572 | + if ( $title && $lower === $wgContLang->lc( $title->getPrefixedText() ) ) { |
| 573 | + return $lower; |
| 574 | + } else { |
| 575 | + return false; |
| 576 | + } |
| 577 | + } |
| 578 | + |
| 579 | + public function isValidTag( $tag ) { |
| 580 | + return ( $this->normalizeTag( $tag ) !== false ); |
| 581 | + } |
| 582 | + |
| 583 | + public function getTestRuns() { |
| 584 | + $dbr = wfGetDB( DB_SLAVE ); |
| 585 | + $result = $dbr->select( |
| 586 | + array( |
| 587 | + 'code_test_suite', |
| 588 | + 'code_test_run', |
| 589 | + ), |
| 590 | + '*', |
| 591 | + array( |
| 592 | + 'ctsuite_repo_id' => $this->mRepoId, |
| 593 | + 'ctsuite_id=ctrun_suite_id', |
| 594 | + 'ctrun_rev_id' => $this->mId, |
| 595 | + ), |
| 596 | + __METHOD__ ); |
| 597 | + $runs = array(); |
| 598 | + foreach( $result as $row ) { |
| 599 | + $suite = CodeTestSuite::newFromRow( $this->mRepo, $row ); |
| 600 | + $runs[] = new CodeTestRun( $suite, $row ); |
| 601 | + } |
| 602 | + return $runs; |
| 603 | + } |
| 604 | + |
| 605 | + public function getTestResults( $success = null ) { |
| 606 | + $dbr = wfGetDB( DB_SLAVE ); |
| 607 | + $conds = array( |
| 608 | + 'ctr_repo_id' => $this->mRepoId, |
| 609 | + 'ctr_rev_id' => $this->mId, |
| 610 | + 'ctr_case_id=ctc_id', |
| 611 | + 'ctc_suite_id=cts_id' ); |
| 612 | + if( $success === true ) { |
| 613 | + $conds['ctr_result'] = 1; |
| 614 | + } elseif( $success === false ) { |
| 615 | + $conds['ctr_result'] = 0; |
| 616 | + } |
| 617 | + $results = $dbr->select( |
| 618 | + array( |
| 619 | + 'code_test_result', |
| 620 | + 'code_test_case', |
| 621 | + 'code_test_suite', |
| 622 | + ), |
| 623 | + '*', |
| 624 | + $conds, |
| 625 | + __METHOD__ ); |
| 626 | + $out = array(); |
| 627 | + foreach( $results as $row ) { |
| 628 | + $out[] = new CodeTestResult( $row ); |
| 629 | + } |
| 630 | + return $out; |
| 631 | + } |
| 632 | + |
| 633 | + public function getPrevious() { |
| 634 | + // hack! |
| 635 | + if ( $this->mId > 1 ) { |
| 636 | + return $this->mId - 1; |
| 637 | + } else { |
| 638 | + return false; |
| 639 | + } |
| 640 | + } |
| 641 | + |
| 642 | + public function getNext() { |
| 643 | + $dbr = wfGetDB( DB_SLAVE ); |
| 644 | + $encId = $dbr->addQuotes( $this->mId ); |
| 645 | + $row = $dbr->selectRow( 'code_rev', |
| 646 | + 'cr_id', |
| 647 | + array( |
| 648 | + 'cr_repo_id' => $this->mRepoId, |
| 649 | + "cr_id > $encId" ), |
| 650 | + __METHOD__, |
| 651 | + array( |
| 652 | + 'ORDER BY' => 'cr_repo_id, cr_id', |
| 653 | + 'LIMIT' => 1 ) ); |
| 654 | + |
| 655 | + if ( $row ) { |
| 656 | + return intval( $row->cr_id ); |
| 657 | + } else { |
| 658 | + return false; |
| 659 | + } |
| 660 | + } |
| 661 | + |
| 662 | + public function getNextUnresolved() { |
| 663 | + $dbr = wfGetDB( DB_SLAVE ); |
| 664 | + $encId = $dbr->addQuotes( $this->mId ); |
| 665 | + $row = $dbr->selectRow( 'code_rev', |
| 666 | + 'cr_id', |
| 667 | + array( |
| 668 | + 'cr_repo_id' => $this->mRepoId, |
| 669 | + "cr_id > $encId", |
| 670 | + 'cr_status' => array( 'new', 'fixme' ) ), |
| 671 | + __METHOD__, |
| 672 | + array( |
| 673 | + 'ORDER BY' => 'cr_repo_id, cr_id', |
| 674 | + 'LIMIT' => 1 ) |
| 675 | + ); |
| 676 | + if ( $row ) { |
| 677 | + return intval( $row->cr_id ); |
| 678 | + } else { |
| 679 | + return false; |
| 680 | + } |
| 681 | + } |
| 682 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/CodeRevision.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 683 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/backend/CodeTestResult.php |
— | — | @@ -0,0 +1,11 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class CodeTestResult { |
| 5 | + function __construct( CodeTestRun $run, $row ) { |
| 6 | + $this->run = $run; |
| 7 | + $this->id = $row->ctresult_id; |
| 8 | + $this->caseId = $row->ctresult_case_id; |
| 9 | + $this->caseName = $row->ctcase_name; |
| 10 | + $this->success = (bool)$row->ctresult_success; |
| 11 | + } |
| 12 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/CodeTestResult.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 13 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/backend/CodeRepository.php |
— | — | @@ -0,0 +1,359 @@ |
| 2 | +<?php |
| 3 | +if ( !defined( 'MEDIAWIKI' ) ) die(); |
| 4 | + |
| 5 | +class CodeRepository { |
| 6 | + static $userLinks = array(); |
| 7 | + |
| 8 | + public static function newFromName( $name ) { |
| 9 | + $dbw = wfGetDB( DB_MASTER ); |
| 10 | + $row = $dbw->selectRow( |
| 11 | + 'code_repo', |
| 12 | + array( |
| 13 | + 'repo_id', |
| 14 | + 'repo_name', |
| 15 | + 'repo_path', |
| 16 | + 'repo_viewvc', |
| 17 | + 'repo_bugzilla' ), |
| 18 | + array( 'repo_name' => $name ), |
| 19 | + __METHOD__ ); |
| 20 | + |
| 21 | + if ( $row ) { |
| 22 | + return self::newFromRow( $row ); |
| 23 | + } else { |
| 24 | + return null; |
| 25 | + } |
| 26 | + } |
| 27 | + |
| 28 | + public static function newFromId( $id ) { |
| 29 | + $dbw = wfGetDB( DB_MASTER ); |
| 30 | + $row = $dbw->selectRow( |
| 31 | + 'code_repo', |
| 32 | + array( |
| 33 | + 'repo_id', |
| 34 | + 'repo_name', |
| 35 | + 'repo_path', |
| 36 | + 'repo_viewvc', |
| 37 | + 'repo_bugzilla' ), |
| 38 | + array( 'repo_id' => intval( $id ) ), |
| 39 | + __METHOD__ ); |
| 40 | + |
| 41 | + if ( $row ) { |
| 42 | + return self::newFromRow( $row ); |
| 43 | + } else { |
| 44 | + return null; |
| 45 | + } |
| 46 | + } |
| 47 | + |
| 48 | + static function newFromRow( $row ) { |
| 49 | + $repo = new CodeRepository(); |
| 50 | + $repo->mId = intval( $row->repo_id ); |
| 51 | + $repo->mName = $row->repo_name; |
| 52 | + $repo->mPath = $row->repo_path; |
| 53 | + $repo->mViewVc = $row->repo_viewvc; |
| 54 | + $repo->mBugzilla = $row->repo_bugzilla; |
| 55 | + return $repo; |
| 56 | + } |
| 57 | + |
| 58 | + static function getRepoList() { |
| 59 | + $dbr = wfGetDB( DB_SLAVE ); |
| 60 | + $res = $dbr->select( 'code_repo', '*', array(), __METHOD__ ); |
| 61 | + $repos = array(); |
| 62 | + foreach ( $res as $row ) { |
| 63 | + $repos[] = self::newFromRow( $row ); |
| 64 | + } |
| 65 | + return $repos; |
| 66 | + } |
| 67 | + |
| 68 | + public function getId() { |
| 69 | + return intval( $this->mId ); |
| 70 | + } |
| 71 | + |
| 72 | + public function getName() { |
| 73 | + return $this->mName; |
| 74 | + } |
| 75 | + |
| 76 | + public function getPath() { |
| 77 | + return $this->mPath; |
| 78 | + } |
| 79 | + |
| 80 | + public function getViewVcBase() { |
| 81 | + return $this->mViewVc; |
| 82 | + } |
| 83 | + |
| 84 | + /** |
| 85 | + * Return a bug URL or false. |
| 86 | + */ |
| 87 | + public function getBugPath( $bugId ) { |
| 88 | + if ( $this->mBugzilla ) { |
| 89 | + return str_replace( '$1', |
| 90 | + urlencode( $bugId ), $this->mBugzilla ); |
| 91 | + } |
| 92 | + return false; |
| 93 | + } |
| 94 | + |
| 95 | + public function getLastStoredRev() { |
| 96 | + $dbr = wfGetDB( DB_SLAVE ); |
| 97 | + $row = $dbr->selectField( |
| 98 | + 'code_rev', |
| 99 | + 'MAX(cr_id)', |
| 100 | + array( 'cr_repo_id' => $this->getId() ), |
| 101 | + __METHOD__ |
| 102 | + ); |
| 103 | + return intval( $row ); |
| 104 | + } |
| 105 | + |
| 106 | + public function getAuthorList() { |
| 107 | + global $wgMemc; |
| 108 | + $key = wfMemcKey( 'codereview', 'authors', $this->getId() ); |
| 109 | + $authors = $wgMemc->get( $key ); |
| 110 | + if ( is_array( $authors ) ) { |
| 111 | + return $authors; |
| 112 | + } |
| 113 | + $dbr = wfGetDB( DB_SLAVE ); |
| 114 | + $res = $dbr->select( |
| 115 | + 'code_rev', |
| 116 | + array( 'cr_author', 'MAX(cr_timestamp) AS time' ), |
| 117 | + array( 'cr_repo_id' => $this->getId() ), |
| 118 | + __METHOD__, |
| 119 | + array( 'GROUP BY' => 'cr_author', |
| 120 | + 'ORDER BY' => 'time DESC', 'LIMIT' => 500 ) |
| 121 | + ); |
| 122 | + $authors = array(); |
| 123 | + while ( $row = $dbr->fetchObject( $res ) ) { |
| 124 | + $authors[] = $row->cr_author; |
| 125 | + } |
| 126 | + $wgMemc->set( $key, $authors, 3600 * 24 * 3 ); |
| 127 | + return $authors; |
| 128 | + } |
| 129 | + |
| 130 | + public function getTagList() { |
| 131 | + global $wgMemc; |
| 132 | + $key = wfMemcKey( 'codereview', 'tags', $this->getId() ); |
| 133 | + $tags = $wgMemc->get( $key ); |
| 134 | + if ( is_array( $tags ) ) { |
| 135 | + return $tags; |
| 136 | + } |
| 137 | + $dbr = wfGetDB( DB_SLAVE ); |
| 138 | + $res = $dbr->select( |
| 139 | + 'code_tags', |
| 140 | + array( 'ct_tag', 'COUNT(*) AS revs' ), |
| 141 | + array( 'ct_repo_id' => $this->getId() ), |
| 142 | + __METHOD__, |
| 143 | + array( 'GROUP BY' => 'ct_tag', |
| 144 | + 'ORDER BY' => 'revs DESC', 'LIMIT' => 500 ) |
| 145 | + ); |
| 146 | + $tags = array(); |
| 147 | + while ( $row = $dbr->fetchObject( $res ) ) { |
| 148 | + $tags[] = $row->ct_tag; |
| 149 | + } |
| 150 | + $wgMemc->set( $key, $tags, 3600 * 24 * 3 ); |
| 151 | + return $tags; |
| 152 | + } |
| 153 | + |
| 154 | + /** |
| 155 | + * Load a particular revision out of the DB |
| 156 | + */ |
| 157 | + public function getRevision( $id ) { |
| 158 | + if ( !$this->isValidRev( $id ) ) { |
| 159 | + return null; |
| 160 | + } |
| 161 | + $dbr = wfGetDB( DB_SLAVE ); |
| 162 | + $row = $dbr->selectRow( 'code_rev', |
| 163 | + '*', |
| 164 | + array( |
| 165 | + 'cr_id' => $id, |
| 166 | + 'cr_repo_id' => $this->getId(), |
| 167 | + ), |
| 168 | + __METHOD__ |
| 169 | + ); |
| 170 | + if ( !$row ) |
| 171 | + throw new MWException( 'Failed to load expected revision data' ); |
| 172 | + return CodeRevision::newFromRow( $this, $row ); |
| 173 | + } |
| 174 | + |
| 175 | + /** |
| 176 | + * Load test suite information |
| 177 | + */ |
| 178 | + public function getTestSuite( $name ) { |
| 179 | + $dbr = wfGetDB( DB_SLAVE ); |
| 180 | + $row = $dbr->selectRow( 'code_test_suite', |
| 181 | + '*', |
| 182 | + array( |
| 183 | + 'ctsuite_repo_id' => $this->getId(), |
| 184 | + 'ctsuite_name' => $name, |
| 185 | + ), |
| 186 | + __METHOD__ |
| 187 | + ); |
| 188 | + if( $row ) { |
| 189 | + return CodeTestSuite::newFromRow( $this, $row ); |
| 190 | + } else { |
| 191 | + return null; |
| 192 | + } |
| 193 | + } |
| 194 | + |
| 195 | + /** |
| 196 | + * @param int $rev Revision ID |
| 197 | + * @param $useCache 'skipcache' to avoid caching |
| 198 | + * 'cached' to *only* fetch if cached |
| 199 | + */ |
| 200 | + public function getDiff( $rev, $useCache = '' ) { |
| 201 | + global $wgMemc; |
| 202 | + wfProfileIn( __METHOD__ ); |
| 203 | + |
| 204 | + $rev1 = $rev - 1; |
| 205 | + $rev2 = $rev; |
| 206 | + |
| 207 | + $revision = $this->getRevision( $rev ); |
| 208 | + if ( $revision == null || !$revision->isDiffable() ) { |
| 209 | + wfProfileOut( __METHOD__ ); |
| 210 | + return false; |
| 211 | + } |
| 212 | + |
| 213 | + # Try memcached... |
| 214 | + $key = wfMemcKey( 'svn', md5( $this->mPath ), 'diff', $rev1, $rev2 ); |
| 215 | + if ( $useCache === 'skipcache' ) { |
| 216 | + $data = NULL; |
| 217 | + } else { |
| 218 | + $data = $wgMemc->get( $key ); |
| 219 | + } |
| 220 | + |
| 221 | + # Try the database... |
| 222 | + if ( !$data && $useCache != 'skipcache' ) { |
| 223 | + $dbr = wfGetDB( DB_SLAVE ); |
| 224 | + $row = $dbr->selectRow( 'code_rev', |
| 225 | + array( 'cr_diff', 'cr_flags' ), |
| 226 | + array( 'cr_repo_id' => $this->mId, 'cr_id' => $rev, 'cr_diff IS NOT NULL' ), |
| 227 | + __METHOD__ |
| 228 | + ); |
| 229 | + if ( $row ) { |
| 230 | + $flags = explode( ',', $row->cr_flags ); |
| 231 | + $data = $row->cr_diff; |
| 232 | + // If the text was fetched without an error, convert it |
| 233 | + if ( $data !== false && in_array( 'gzip', $flags ) ) { |
| 234 | + # Deal with optional compression of archived pages. |
| 235 | + # This can be done periodically via maintenance/compressOld.php, and |
| 236 | + # as pages are saved if $wgCompressRevisions is set. |
| 237 | + $data = gzinflate( $data ); |
| 238 | + } |
| 239 | + } |
| 240 | + } |
| 241 | + |
| 242 | + # Generate the diff as needed... |
| 243 | + if ( !$data && $useCache !== 'cached' ) { |
| 244 | + $svn = SubversionAdaptor::newFromRepo( $this->mPath ); |
| 245 | + $data = $svn->getDiff( '', $rev1, $rev2 ); |
| 246 | + // Store to cache |
| 247 | + $wgMemc->set( $key, $data, 3600 * 24 * 3 ); |
| 248 | + // Permanent DB storage |
| 249 | + $storedData = $data; |
| 250 | + $flags = Revision::compressRevisionText( $storedData ); |
| 251 | + $dbw = wfGetDB( DB_MASTER ); |
| 252 | + $dbw->update( 'code_rev', |
| 253 | + array( 'cr_diff' => $storedData, 'cr_flags' => $flags ), |
| 254 | + array( 'cr_repo_id' => $this->mId, 'cr_id' => $rev ), |
| 255 | + __METHOD__ |
| 256 | + ); |
| 257 | + } |
| 258 | + wfProfileOut( __METHOD__ ); |
| 259 | + return $data; |
| 260 | + } |
| 261 | + |
| 262 | + /** |
| 263 | + * Is the requested revid a valid revision to show? |
| 264 | + * @return bool |
| 265 | + * @param $rev int Rev id to check |
| 266 | + */ |
| 267 | + public function isValidRev( $rev ) { |
| 268 | + $rev = intval( $rev ); |
| 269 | + if ( $rev > 0 && $rev <= $this->getLastStoredRev() ) { |
| 270 | + return true; |
| 271 | + } |
| 272 | + return false; |
| 273 | + } |
| 274 | + |
| 275 | + /* |
| 276 | + * Link the $author to the wikiuser $user |
| 277 | + * @param string $author |
| 278 | + * @param User $user |
| 279 | + * @return bool success |
| 280 | + */ |
| 281 | + public function linkUser( $author, User $user ) { |
| 282 | + // We must link to an existing user |
| 283 | + if ( !$user->getId() ) { |
| 284 | + return false; |
| 285 | + } |
| 286 | + $dbw = wfGetDB( DB_MASTER ); |
| 287 | + // Insert in the auther -> user link row. |
| 288 | + // Skip existing rows. |
| 289 | + $dbw->insert( 'code_authors', |
| 290 | + array( |
| 291 | + 'ca_repo_id' => $this->getId(), |
| 292 | + 'ca_author' => $author, |
| 293 | + 'ca_user_text' => $user->getName() |
| 294 | + ), |
| 295 | + __METHOD__, |
| 296 | + array( 'IGNORE' ) |
| 297 | + ); |
| 298 | + // If the last query already found a row, then update it. |
| 299 | + if ( !$dbw->affectedRows() ) { |
| 300 | + $dbw->update( |
| 301 | + 'code_authors', |
| 302 | + array( 'ca_user_text' => $user->getName() ), |
| 303 | + array( |
| 304 | + 'ca_repo_id' => $this->getId(), |
| 305 | + 'ca_author' => $author, |
| 306 | + ), |
| 307 | + __METHOD__ |
| 308 | + ); |
| 309 | + } |
| 310 | + self::$userLinks[$author] = $user; |
| 311 | + return ( $dbw->affectedRows() > 0 ); |
| 312 | + } |
| 313 | + |
| 314 | + /* |
| 315 | + * Remove local user links for $author |
| 316 | + * @param string $author |
| 317 | + * @return bool success |
| 318 | + */ |
| 319 | + public function unlinkUser( $author ) { |
| 320 | + $dbw = wfGetDB( DB_MASTER ); |
| 321 | + $dbw->delete( |
| 322 | + 'code_authors', |
| 323 | + array( |
| 324 | + 'ca_repo_id' => $this->getId(), |
| 325 | + 'ca_author' => $author, |
| 326 | + ), |
| 327 | + __METHOD__ |
| 328 | + ); |
| 329 | + self::$userLinks[$author] = false; |
| 330 | + return ( $dbw->affectedRows() > 0 ); |
| 331 | + } |
| 332 | + |
| 333 | + /* |
| 334 | + * returns a User object if $author has a wikiuser associated, |
| 335 | + * or false |
| 336 | + */ |
| 337 | + public function authorWikiUser( $author ) { |
| 338 | + if ( isset( self::$userLinks[$author] ) ) |
| 339 | + return self::$userLinks[$author]; |
| 340 | + |
| 341 | + $dbr = wfGetDB( DB_SLAVE ); |
| 342 | + $wikiUser = $dbr->selectField( |
| 343 | + 'code_authors', |
| 344 | + 'ca_user_text', |
| 345 | + array( |
| 346 | + 'ca_repo_id' => $this->getId(), |
| 347 | + 'ca_author' => $author, |
| 348 | + ), |
| 349 | + __METHOD__ |
| 350 | + ); |
| 351 | + $user = null; |
| 352 | + if ( $wikiUser ) |
| 353 | + $user = User::newFromName( $wikiUser ); |
| 354 | + if ( $user instanceof User ) |
| 355 | + $res = $user; |
| 356 | + else |
| 357 | + $res = false; |
| 358 | + return self::$userLinks[$author] = $res; |
| 359 | + } |
| 360 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/CodeRepository.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 361 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/api/ApiCodeUpdate.php |
— | — | @@ -0,0 +1,104 @@ |
| 2 | +<?php
|
| 3 | +
|
| 4 | +class ApiCodeUpdate extends ApiBase {
|
| 5 | +
|
| 6 | + public function execute() {
|
| 7 | + global $wgUser;
|
| 8 | + // Before doing anything at all, let's check permissions
|
| 9 | + if( !$wgUser->isAllowed('codereview-use') ) {
|
| 10 | + $this->dieUsage('You don\'t have permission update code','permissiondenied');
|
| 11 | + }
|
| 12 | + $params = $this->extractRequestParams();
|
| 13 | +
|
| 14 | + if ( !isset( $params['repo'] ) ) {
|
| 15 | + $this->dieUsageMsg( array( 'missingparam', 'repo' ) );
|
| 16 | + }
|
| 17 | + if ( !isset( $params['rev'] ) ) {
|
| 18 | + $this->dieUsageMsg( array( 'missingparam', 'rev' ) );
|
| 19 | + }
|
| 20 | +
|
| 21 | + $repo = CodeRepository::newFromName( $params['repo'] );
|
| 22 | + if ( !$repo ) {
|
| 23 | + $this->dieUsage( "Invalid repo ``{$params['repo']}''", 'invalidrepo' );
|
| 24 | + }
|
| 25 | +
|
| 26 | + $svn = SubversionAdaptor::newFromRepo( $repo->getPath() );
|
| 27 | + $lastStoredRev = $repo->getLastStoredRev();
|
| 28 | +
|
| 29 | + if ( $lastStoredRev >= $params['rev'] ) {
|
| 30 | + // Nothing to do, we're up to date.
|
| 31 | + // Return an empty result
|
| 32 | + $this->getResult()->addValue( null, $this->getModuleName(), array() );
|
| 33 | + return;
|
| 34 | + }
|
| 35 | +
|
| 36 | + // FIXME: this could be a lot?
|
| 37 | + $log = $svn->getLog( '', $lastStoredRev + 1, $params['rev'] );
|
| 38 | + if ( !$log ) {
|
| 39 | + // FIXME: When and how often does this happen?
|
| 40 | + // Should we use dieUsage() here instead?
|
| 41 | + ApiBase::dieDebug( __METHOD__, "Something awry..." );
|
| 42 | + }
|
| 43 | +
|
| 44 | + $result = array();
|
| 45 | + foreach ( $log as $data ) {
|
| 46 | + $codeRev = CodeRevision::newFromSvn( $repo, $data );
|
| 47 | + $codeRev->save();
|
| 48 | + $result[] = array(
|
| 49 | + 'id' => $codeRev->getId(),
|
| 50 | + 'author' => $codeRev->getAuthor(),
|
| 51 | + 'timestamp' => wfTimestamp( TS_ISO_8601, $codeRev->getTimestamp() ),
|
| 52 | + 'message' => $codeRev->getMessage()
|
| 53 | + );
|
| 54 | + }
|
| 55 | + // Cache the diffs if there are a only a few.
|
| 56 | + // Mainly for WMF post-commit ping hook...
|
| 57 | + if ( count( $result ) <= 2 ) {
|
| 58 | + foreach ( $result as $revData ) {
|
| 59 | + $diff = $repo->getDiff( $revData['id'] ); // trigger caching
|
| 60 | + }
|
| 61 | + }
|
| 62 | + $this->getResult()->setIndexedTagName( $result, 'rev' );
|
| 63 | + $this->getResult()->addValue( null, $this->getModuleName(), $result );
|
| 64 | + }
|
| 65 | +
|
| 66 | + public function mustBePosted() {
|
| 67 | + // Discourage casual browsing :)
|
| 68 | + return true;
|
| 69 | + }
|
| 70 | +
|
| 71 | + public function isWriteMode() {
|
| 72 | + return true;
|
| 73 | + }
|
| 74 | +
|
| 75 | + public function getAllowedParams() {
|
| 76 | + return array(
|
| 77 | + 'repo' => null,
|
| 78 | + 'rev' => array(
|
| 79 | + ApiBase::PARAM_TYPE => 'integer',
|
| 80 | + ApiBase::PARAM_MIN => 1
|
| 81 | + )
|
| 82 | + );
|
| 83 | + }
|
| 84 | +
|
| 85 | + public function getParamDescription() {
|
| 86 | + return array(
|
| 87 | + 'repo' => 'Name of repository to update',
|
| 88 | + 'rev' => 'Revision ID number to update to' );
|
| 89 | + }
|
| 90 | +
|
| 91 | + public function getDescription() {
|
| 92 | + return array(
|
| 93 | + 'Update CodeReview repository data from master revision control system.' );
|
| 94 | + }
|
| 95 | +
|
| 96 | + public function getExamples() {
|
| 97 | + return array(
|
| 98 | + 'api.php?action=codeupdate&repo=MediaWiki&rev=42080',
|
| 99 | + );
|
| 100 | + }
|
| 101 | +
|
| 102 | + public function getVersion() {
|
| 103 | + return __CLASS__ . ': $Id: ApiCodeUpdate.php 48928 2009-03-27 18:41:20Z catrope $';
|
| 104 | + }
|
| 105 | +}
|
Index: branches/wmf-deployment/extensions/CodeReview/api/ApiCodeTestUpload.php |
— | — | @@ -0,0 +1,119 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class ApiCodeTestUpload extends ApiBase { |
| 5 | + |
| 6 | + public function execute() { |
| 7 | + global $wgUser; |
| 8 | + // Before doing anything at all, let's check permissions |
| 9 | + if( !$wgUser->isAllowed('codereview-use') ) { |
| 10 | + $this->dieUsage('You don\'t have permission to upload test results', 'permissiondenied'); |
| 11 | + } |
| 12 | + $params = $this->extractRequestParams(); |
| 13 | + |
| 14 | + $this->validateParams( $params ); |
| 15 | + $this->validateHmac( $params ); |
| 16 | + |
| 17 | + $repo = CodeRepository::newFromName( $params['repo'] ); |
| 18 | + if( !$repo ) { |
| 19 | + $this->dieUsage( "Invalid repo ``{$params['repo']}''", 'invalidrepo' ); |
| 20 | + } |
| 21 | + |
| 22 | + $suite = $repo->getTestSuite( $params['suite'] ); |
| 23 | + if( !$suite ) { |
| 24 | + $this->dieUsage( "Invalid test suite ``{$params['suite']}''", 'invalidsuite' ); |
| 25 | + } |
| 26 | + |
| 27 | + // Note that we might be testing a revision that hasn't gotten slurped in yet, |
| 28 | + // so we won't reject data for revisions we don't know about yet. |
| 29 | + $revId = intval( $params['rev'] ); |
| 30 | + |
| 31 | + $status = $params['status']; |
| 32 | + if( $status == 'running' || $status == 'aborted' ) { |
| 33 | + // Set the 'tests running' flag so we can mark it... |
| 34 | + $suite->setStatus( $revId, $status ); |
| 35 | + } elseif( $status == 'complete' ) { |
| 36 | + // Save data and mark running test as completed. |
| 37 | + $results = json_decode( $params['results'], true ); |
| 38 | + if( !is_array( $results ) ) { |
| 39 | + $this->dieUsage( "Invalid test result data", 'invalidresults' ); |
| 40 | + } |
| 41 | + $suite->saveResults( $revId, $results ); |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + protected function validateParams( $params ) { |
| 46 | + $required = array( 'repo', 'suite', 'rev', 'status', 'hmac' ); |
| 47 | + if( isset( $params['status'] ) && $params['status'] == 'complete' ) { |
| 48 | + $required[] = 'results'; |
| 49 | + } |
| 50 | + foreach( $required as $arg ) { |
| 51 | + if ( !isset( $params[$arg] ) ) { |
| 52 | + $this->dieUsageMsg( array( 'missingparam', $arg ) ); |
| 53 | + } |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + protected function validateHmac( $params ) { |
| 58 | + global $wgCodeReviewSharedSecret; |
| 59 | + |
| 60 | + // Generate a hash MAC to validate our credentials |
| 61 | + $message = array( |
| 62 | + $params['repo'], |
| 63 | + $params['suite'], |
| 64 | + $params['rev'], |
| 65 | + $params['status'], |
| 66 | + ); |
| 67 | + if( $params['status'] == "complete" ) { |
| 68 | + $message[] = $params['results']; |
| 69 | + } |
| 70 | + $hmac = hash_hmac( "sha1", implode( "|", $message ), $wgCodeReviewSharedSecret ); |
| 71 | + if( $hmac != $params['hmac'] ) { |
| 72 | + $this->dieUsageMsg( array( 'invalidhmac', $params['hmac'] ) ); |
| 73 | + } |
| 74 | + } |
| 75 | + |
| 76 | + public function mustBePosted() { |
| 77 | + // Discourage casual browsing :) |
| 78 | + return true; |
| 79 | + } |
| 80 | + |
| 81 | + public function isWriteMode() { |
| 82 | + return true; |
| 83 | + } |
| 84 | + |
| 85 | + public function getAllowedParams() { |
| 86 | + return array( |
| 87 | + 'repo' => null, |
| 88 | + 'suite' => null, |
| 89 | + 'rev' => array( |
| 90 | + ApiBase::PARAM_TYPE => 'integer', |
| 91 | + ApiBase::PARAM_MIN => 1 |
| 92 | + ), |
| 93 | + 'status' => array( |
| 94 | + ApiBase::PARAM_TYPE => array( 'running', 'complete', 'abort' ), |
| 95 | + ), |
| 96 | + 'hmac' => null, |
| 97 | + 'results' => null, |
| 98 | + ); |
| 99 | + } |
| 100 | + |
| 101 | + public function getParamDescription() { |
| 102 | + return array( |
| 103 | + 'repo' => 'Name of repository to update', |
| 104 | + 'suite' => 'Name of test suite to record run results for', |
| 105 | + 'rev' => 'Revision ID tests were run against', |
| 106 | + 'status' => 'Status of test run', |
| 107 | + 'hmac' => 'HMAC validation', |
| 108 | + 'results' => 'JSON-encoded map of test names to success results, for status "complete"', |
| 109 | + ); |
| 110 | + } |
| 111 | + |
| 112 | + public function getDescription() { |
| 113 | + return array( |
| 114 | + 'Upload CodeReview test run results from a test runner.' ); |
| 115 | + } |
| 116 | + |
| 117 | + public function getVersion() { |
| 118 | + return __CLASS__ . ': $Id: ApiCodeUpdate.php 48928 2009-03-27 18:41:20Z catrope $'; |
| 119 | + } |
| 120 | +} |
Property changes on: branches/wmf-deployment/extensions/CodeReview/api/ApiCodeTestUpload.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 121 | + native |
Index: branches/wmf-deployment/extensions/CodeReview/api/ApiCodeComments.php |
— | — | @@ -0,0 +1,139 @@ |
| 2 | +<?php
|
| 3 | +
|
| 4 | +/*
|
| 5 | + * Created on Oct 29, 2008
|
| 6 | + *
|
| 7 | + * API for MediaWiki 1.8+
|
| 8 | + *
|
| 9 | + * Copyright (C) 2008 Bryan Tong Minh <Bryan.TongMinh@Gmail.com>
|
| 10 | + *
|
| 11 | + * This program is free software; you can redistribute it and/or modify
|
| 12 | + * it under the terms of the GNU General Public License as published by
|
| 13 | + * the Free Software Foundation; either version 2 of the License, or
|
| 14 | + * (at your option) any later version.
|
| 15 | + *
|
| 16 | + * This program is distributed in the hope that it will be useful,
|
| 17 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 18 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 19 | + * GNU General Public License for more details.
|
| 20 | + *
|
| 21 | + * You should have received a copy of the GNU General Public License along
|
| 22 | + * with this program; if not, write to the Free Software Foundation, Inc.,
|
| 23 | + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
| 24 | + * http://www.gnu.org/copyleft/gpl.html
|
| 25 | + */
|
| 26 | +
|
| 27 | +class ApiCodeComments extends ApiQueryBase {
|
| 28 | + public function __construct( $query, $moduleName ) {
|
| 29 | + parent::__construct( $query, $moduleName, 'cc' );
|
| 30 | + }
|
| 31 | +
|
| 32 | + public function execute() {
|
| 33 | + global $wgUser;
|
| 34 | + // Before doing anything at all, let's check permissions
|
| 35 | + if( !$wgUser->isAllowed('codereview-use') ) {
|
| 36 | + $this->dieUsage('You don\'t have permission to view code comments','permissiondenied');
|
| 37 | + }
|
| 38 | + $params = $this->extractRequestParams();
|
| 39 | + if ( is_null( $params['repo'] ) )
|
| 40 | + $this->dieUsageMsg( array( 'missingparam', 'repo' ) );
|
| 41 | + $this->props = array_flip( $params['prop'] );
|
| 42 | +
|
| 43 | + $listview = new CodeCommentsListView( $params['repo'] );
|
| 44 | + if ( is_null( $listview->getRepo() ) )
|
| 45 | + $this->dieUsage( "Invalid repo ``{$params['repo']}''", 'invalidrepo' );
|
| 46 | + $pager = $listview->getPager();
|
| 47 | +
|
| 48 | + if ( !is_null( $params['start'] ) )
|
| 49 | + $pager->setOffset( $this->getDB()->timestamp( $params['start'] ) );
|
| 50 | + $limit = $params['limit'];
|
| 51 | + $pager->setLimit( $limit );
|
| 52 | +
|
| 53 | + $pager->doQuery();
|
| 54 | +
|
| 55 | + $comments = $pager->getResult();
|
| 56 | + $data = array();
|
| 57 | +
|
| 58 | + $count = 0;
|
| 59 | + $lastTimestamp = 0;
|
| 60 | + while ( $row = $comments->fetchObject() ) {
|
| 61 | + if ( $count == $limit ) {
|
| 62 | + $this->setContinueEnumParameter( 'start',
|
| 63 | + wfTimestamp( TS_ISO_8601, $lastTimestamp ) );
|
| 64 | + break;
|
| 65 | + }
|
| 66 | +
|
| 67 | + $data[] = $this->formatRow( $row );
|
| 68 | + $lastTimestamp = $row->cc_timestamp;
|
| 69 | + $count++;
|
| 70 | + }
|
| 71 | + $comments->free();
|
| 72 | +
|
| 73 | + $result = $this->getResult();
|
| 74 | + $result->setIndexedTagName( $data, 'comment' );
|
| 75 | + $result->addValue( 'query', $this->getModuleName(), $data );
|
| 76 | + }
|
| 77 | +
|
| 78 | + private function formatRow( $row ) {
|
| 79 | + $item = array();
|
| 80 | + if ( isset( $this->props['timestamp'] ) )
|
| 81 | + $item['timestamp'] = wfTimestamp( TS_ISO_8601, $row->cc_timestamp );
|
| 82 | + if ( isset( $this->props['user'] ) )
|
| 83 | + $item['user'] = $row->cc_user_text;
|
| 84 | + if ( isset( $this->props['revision'] ) )
|
| 85 | + $item['status'] = $row->cr_status;
|
| 86 | + if ( isset( $this->props['text'] ) )
|
| 87 | + ApiResult::setContent( $item, $row->cc_text );
|
| 88 | + return $item;
|
| 89 | + }
|
| 90 | +
|
| 91 | + public function getAllowedParams() {
|
| 92 | + return array (
|
| 93 | + 'repo' => null,
|
| 94 | + 'limit' => array (
|
| 95 | + ApiBase :: PARAM_DFLT => 10,
|
| 96 | + ApiBase :: PARAM_TYPE => 'limit',
|
| 97 | + ApiBase :: PARAM_MIN => 1,
|
| 98 | + ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
|
| 99 | + ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
|
| 100 | + ),
|
| 101 | + 'start' => array(
|
| 102 | + ApiBase :: PARAM_TYPE => 'timestamp'
|
| 103 | + ),
|
| 104 | + 'prop' => array (
|
| 105 | + ApiBase :: PARAM_ISMULTI => true,
|
| 106 | + ApiBase :: PARAM_DFLT => 'timestamp|user|revision',
|
| 107 | + ApiBase :: PARAM_TYPE => array (
|
| 108 | + 'timestamp',
|
| 109 | + 'user',
|
| 110 | + 'revision',
|
| 111 | + 'text',
|
| 112 | + ),
|
| 113 | + ),
|
| 114 | + );
|
| 115 | + }
|
| 116 | +
|
| 117 | + public function getParamDescription() {
|
| 118 | + return array(
|
| 119 | + 'repo' => 'Name of the repository',
|
| 120 | + 'limit' => 'How many comments to return',
|
| 121 | + 'start' => 'Timestamp to start listing at',
|
| 122 | + 'prop' => 'Which properties to return',
|
| 123 | + );
|
| 124 | + }
|
| 125 | +
|
| 126 | + public function getDescription() {
|
| 127 | + return 'List comments on revisions in CodeReview';
|
| 128 | + }
|
| 129 | +
|
| 130 | + public function getExamples() {
|
| 131 | + return array(
|
| 132 | + 'api.php?action=query&list=codecomments&ccrepo=MediaWiki',
|
| 133 | + 'api.php?action=query&list=codecomments&ccrepo=MediaWiki&ccprop=timestamp|user|revision|text',
|
| 134 | + );
|
| 135 | + }
|
| 136 | +
|
| 137 | + public function getVersion() {
|
| 138 | + return __CLASS__ . ': $Id: ApiCodeComments.php 48777 2009-03-25 01:26:54Z aaron $';
|
| 139 | + }
|
| 140 | +}
|
Index: branches/wmf-deployment/extensions/CodeReview/api/ApiCodeDiff.php |
— | — | @@ -0,0 +1,80 @@ |
| 2 | +<?php
|
| 3 | +
|
| 4 | +class ApiCodeDiff extends ApiBase {
|
| 5 | +
|
| 6 | + public function execute() {
|
| 7 | + global $wgUser;
|
| 8 | + // Before doing anything at all, let's check permissions
|
| 9 | + if( !$wgUser->isAllowed('codereview-use') ) {
|
| 10 | + $this->dieUsage('You don\'t have permission to view code diffs','permissiondenied');
|
| 11 | + }
|
| 12 | + $params = $this->extractRequestParams();
|
| 13 | +
|
| 14 | + if ( !isset( $params['repo'] ) ) {
|
| 15 | + $this->dieUsageMsg( array( 'missingparam', 'repo' ) );
|
| 16 | + }
|
| 17 | + if ( !isset( $params['rev'] ) ) {
|
| 18 | + $this->dieUsageMsg( array( 'missingparam', 'rev' ) );
|
| 19 | + }
|
| 20 | +
|
| 21 | + $repo = CodeRepository::newFromName( $params['repo'] );
|
| 22 | + if ( !$repo ) {
|
| 23 | + $this->dieUsage( "Invalid repo ``{$params['repo']}''", 'invalidrepo' );
|
| 24 | + }
|
| 25 | +
|
| 26 | + $svn = SubversionAdaptor::newFromRepo( $repo->getPath() );
|
| 27 | + $lastStoredRev = $repo->getLastStoredRev();
|
| 28 | +
|
| 29 | + if ( $params['rev'] > $lastStoredRev ) {
|
| 30 | + $this->dieUsage( "There is no revision with ID {$params['rev']}", 'nosuchrev' );
|
| 31 | + }
|
| 32 | +
|
| 33 | + $diff = $repo->getDiff( $params['rev'] );
|
| 34 | +
|
| 35 | + if ( $diff ) {
|
| 36 | + $hilite = new CodeDiffHighlighter();
|
| 37 | + $html = $hilite->render( $diff );
|
| 38 | + } else {
|
| 39 | + // FIXME: Are we sure we don't want to throw an error here?
|
| 40 | + $html = 'Failed to load diff.';
|
| 41 | + }
|
| 42 | +
|
| 43 | + $data = array(
|
| 44 | + 'repo' => $params['repo'],
|
| 45 | + 'id' => $params['rev'],
|
| 46 | + 'diff' => $html
|
| 47 | + );
|
| 48 | + $this->getResult()->addValue( 'code', 'rev', $data );
|
| 49 | + }
|
| 50 | +
|
| 51 | + public function getAllowedParams() {
|
| 52 | + return array(
|
| 53 | + 'repo' => null,
|
| 54 | + 'rev' => array(
|
| 55 | + ApiBase::PARAM_TYPE => 'integer',
|
| 56 | + ApiBase::PARAM_MIN => 1
|
| 57 | + )
|
| 58 | + );
|
| 59 | + }
|
| 60 | +
|
| 61 | + public function getParamDescription() {
|
| 62 | + return array(
|
| 63 | + 'repo' => 'Name of repository to look at',
|
| 64 | + 'rev' => 'Revision ID to fetch diff of' );
|
| 65 | + }
|
| 66 | +
|
| 67 | + public function getDescription() {
|
| 68 | + return array(
|
| 69 | + 'Fetch formatted diff from CodeReview\'s backing revision control system.' );
|
| 70 | + }
|
| 71 | +
|
| 72 | + public function getExamples() {
|
| 73 | + return array(
|
| 74 | + 'api.php?action=codediff&repo=MediaWiki&rev=42080',
|
| 75 | + );
|
| 76 | + }
|
| 77 | +
|
| 78 | + public function getVersion() {
|
| 79 | + return __CLASS__ . ': $Id: ApiCodeDiff.php 48777 2009-03-25 01:26:54Z aaron $';
|
| 80 | + }
|
| 81 | +}
|
Index: branches/wmf-deployment/extensions/CodeReview/CodeReview.alias.php |
— | — | @@ -19,7 +19,7 @@ |
20 | 20 | * @author Meno25 |
21 | 21 | */ |
22 | 22 | $aliases['ar'] = array( |
23 | | - 'Code' => array( 'كود' ), |
| 23 | + 'Code' => array( 'كود', 'مراجعة_الكود' ), |
24 | 24 | 'RepoAdmin' => array( 'إدارة_المستودع' ), |
25 | 25 | ); |
26 | 26 | |
— | — | @@ -94,6 +94,11 @@ |
95 | 95 | 'RepoAdmin' => array( 'Admin repo' ), |
96 | 96 | ); |
97 | 97 | |
| 98 | +/** Indonesian (Bahasa Indonesia) */ |
| 99 | +$aliases['id'] = array( |
| 100 | + 'Code' => array( 'Kode', 'Tinjauan kode', 'TinjauanKode' ), |
| 101 | +); |
| 102 | + |
98 | 103 | /** Japanese (日本語) */ |
99 | 104 | $aliases['ja'] = array( |
100 | 105 | 'Code' => array( 'コード', 'コードレビュー' ), |
— | — | @@ -160,6 +165,12 @@ |
161 | 166 | 'RepoAdmin' => array( 'रेपोप्रचालक' ), |
162 | 167 | ); |
163 | 168 | |
| 169 | +/** Slovak (Slovenčina) */ |
| 170 | +$aliases['sk'] = array( |
| 171 | + 'Code' => array( 'Kód', 'KontrolaKódu' ), |
| 172 | + 'RepoAdmin' => array( 'SprávcaÚložiska' ), |
| 173 | +); |
| 174 | + |
164 | 175 | /** Swahili (Kiswahili) */ |
165 | 176 | $aliases['sw'] = array( |
166 | 177 | 'Code' => array( 'Kodi', 'Onyesha kodi' ), |
Index: branches/wmf-deployment/extensions/CodeReview/codereview.sql |
— | — | @@ -65,7 +65,7 @@ |
66 | 66 | cr_diff mediumblob NULL, |
67 | 67 | -- Text flags: gzip,utf-8,external |
68 | 68 | cr_flags tinyblob NOT NULL, |
69 | | - |
| 69 | + |
70 | 70 | primary key (cr_repo_id, cr_id), |
71 | 71 | key (cr_repo_id, cr_timestamp), |
72 | 72 | key cr_repo_author (cr_repo_id, cr_author, cr_timestamp) |
— | — | @@ -126,7 +126,7 @@ |
127 | 127 | cf_to int not null, |
128 | 128 | |
129 | 129 | primary key (cf_repo_id, cf_from, cf_to), |
130 | | - key (cf_repo_id, cf_to, cf_from) |
| 130 | + key repo_to_from (cf_repo_id, cf_to, cf_from) |
131 | 131 | ) /*$wgDBTableOptions*/; |
132 | 132 | |
133 | 133 | -- And for our commenting system... |
— | — | @@ -217,3 +217,70 @@ |
218 | 218 | key cpc_repo_rev_time (cpc_repo_id, cpc_rev_id, cpc_timestamp), |
219 | 219 | key cpc_repo_time (cpc_repo_id, cpc_timestamp) |
220 | 220 | ) /*$wgDBTableOptions*/; |
| 221 | + |
| 222 | +-- |
| 223 | +-- Information on available test suites |
| 224 | +DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_suite; |
| 225 | +CREATE TABLE /*$wgDBprefix*/code_test_suite ( |
| 226 | + -- Unique ID per test suite |
| 227 | + ctsuite_id int auto_increment not null, |
| 228 | + |
| 229 | + -- Repository ID of the code base this applies to |
| 230 | + ctsuite_repo_id int not null, |
| 231 | + |
| 232 | + -- Which branch path this applies to, eg '/trunk/phase3' |
| 233 | + ctsuite_branch_path varchar(255) not null, |
| 234 | + |
| 235 | + -- Pleasantly user-readable name, eg "ParserTests" |
| 236 | + ctsuite_name varchar(255) not null, |
| 237 | + |
| 238 | + -- Description... |
| 239 | + ctsuite_desc varchar(255) not null, |
| 240 | + |
| 241 | + primary key ctsuite_id (ctsuite_id) |
| 242 | +) /*$wgDBtableOptions*/; |
| 243 | + |
| 244 | +DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_case; |
| 245 | +CREATE TABLE /*$wgDBprefix*/code_test_case ( |
| 246 | + ctcase_id int auto_increment not null, |
| 247 | + ctcase_suite_id int not null, |
| 248 | + ctcase_name varchar(255) not null, |
| 249 | + |
| 250 | + primary key ctc_id (ctcase_id), |
| 251 | + key (ctcase_suite_id, ctcase_id) |
| 252 | +) /*$wgDBtableOptions*/; |
| 253 | + |
| 254 | +DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_run; |
| 255 | +CREATE TABLE /*$wgDBprefix*/code_test_run ( |
| 256 | + ctrun_id int auto_increment not null, |
| 257 | + |
| 258 | + ctrun_suite_id int not null, |
| 259 | + ctrun_rev_id int not null, |
| 260 | + |
| 261 | + ctrun_status enum ('running', 'complete', 'abort'), |
| 262 | + |
| 263 | + ctrun_count_total int, |
| 264 | + ctrun_count_success int, |
| 265 | + |
| 266 | + primary key ctrun_id (ctrun_id), |
| 267 | + key suite_rev (ctrun_suite_id, ctrun_rev_id) |
| 268 | +) /*$wgDBtableOptions*/; |
| 269 | + |
| 270 | + |
| 271 | +DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_result; |
| 272 | +CREATE TABLE /*$wgDBprefix*/code_test_result ( |
| 273 | + ctresult_id int auto_increment not null, |
| 274 | + |
| 275 | + -- Which test run and case are we on? |
| 276 | + ctresult_run_id int not null, |
| 277 | + ctresult_case_id int not null, |
| 278 | + |
| 279 | + -- Did we succeed or fail? |
| 280 | + ctresult_success bool not null, |
| 281 | + |
| 282 | + -- Optional HTML chunk data |
| 283 | + ctresult_details blob, |
| 284 | + |
| 285 | + primary key ctr_id (ctresult_id), |
| 286 | + key run_id (ctresult_run_id, ctresult_id) |
| 287 | +) /*$wgDBtableOptions*/; |
Property changes on: branches/wmf-deployment/extensions/CodeReview |
___________________________________________________________________ |
Added: svn:mergeinfo |
221 | 288 | Merged /trunk/phase3/extensions/CodeReview:r52290,52402,52404,52718,52737,52759,52776,52791,52800,52808,52812-52813,52815-52819,52822,52846,52850,52852-52853,52855-52857,52859,52879,52924,52986,53128-53129,53190,53197,53199,53203-53204,53210-53211,53247,53249,53252,53267,53369 |
222 | 289 | Merged /trunk/phase3/CodeReview:r52859,53272 |
223 | 290 | Merged /branches/REL1_15/phase3/extensions/CodeReview:r51646 |
224 | 291 | Merged /trunk/extensions/CodeReview:r52089-54191 |