James Moger
2015-11-22 ed552ba47c02779c270ffd62841d6d1048dade70
commit | author | age
82df52 1 /*
JM 2  * Copyright 2011 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.gitblit.wicket.panels;
17
8c9255 18 import java.text.MessageFormat;
JM 19 import java.util.ArrayList;
828add 20 import java.util.HashMap;
8c9255 21 import java.util.List;
828add 22 import java.util.Map;
JM 23
24 import javax.servlet.http.HttpServletRequest;
82df52 25
8c9255 26 import org.apache.wicket.Component;
JM 27 import org.apache.wicket.RequestCycle;
28 import org.apache.wicket.markup.html.basic.Label;
ffbf03 29 import org.apache.wicket.markup.html.image.ContextImage;
JM 30 import org.apache.wicket.markup.html.panel.Fragment;
8c9255 31 import org.apache.wicket.markup.repeater.Item;
JM 32 import org.apache.wicket.markup.repeater.data.DataView;
33 import org.apache.wicket.markup.repeater.data.ListDataProvider;
34 import org.apache.wicket.protocol.http.WebRequest;
ffbf03 35 import org.apache.wicket.protocol.http.request.WebClientInfo;
8c9255 36
JM 37 import com.gitblit.Constants.AccessPermission;
38 import com.gitblit.Constants.AccessRestrictionType;
3b6904 39 import com.gitblit.Keys;
ffbf03 40 import com.gitblit.models.GitClientApplication;
8c9255 41 import com.gitblit.models.RepositoryModel;
828add 42 import com.gitblit.models.RepositoryUrl;
8c9255 43 import com.gitblit.models.UserModel;
3b6904 44 import com.gitblit.utils.StringUtils;
366bec 45 import com.gitblit.wicket.ExternalImage;
8c9255 46 import com.gitblit.wicket.GitBlitWebSession;
82df52 47 import com.gitblit.wicket.WicketUtils;
JM 48
8c9255 49 /**
JM 50  * Smart repository url panel which can display multiple Gitblit repository urls
51  * and also supports 3rd party app clone links.
699e71 52  *
8c9255 53  * @author James Moger
JM 54  *
55  */
82df52 56 public class RepositoryUrlPanel extends BasePanel {
JM 57
58     private static final long serialVersionUID = 1L;
828add 59
JM 60     private final String externalPermission = "?";
61
62     private boolean onlyUrls;
699e71 63     private UserModel user;
828add 64     private RepositoryModel repository;
JM 65     private RepositoryUrl primaryUrl;
66     private Map<String, String> urlPermissionsMap;
67     private Map<AccessRestrictionType, String> accessRestrictionsMap;
699e71 68
828add 69     public RepositoryUrlPanel(String wicketId, boolean onlyUrls, UserModel user, RepositoryModel repository) {
82df52 70         super(wicketId);
828add 71         this.onlyUrls = onlyUrls;
JM 72         this.user = user == null ? UserModel.ANONYMOUS : user;
73         this.repository = repository;
74         this.urlPermissionsMap = new HashMap<String, String>();
75     }
699e71 76
828add 77     @Override
JM 78     protected void onInitialize() {
79         super.onInitialize();
8c9255 80
828add 81         HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
JM 82
7d3a31 83         List<RepositoryUrl> repositoryUrls = app().services().getRepositoryUrls(req, user, repository);
ffbf03 84         // grab primary url from the top of the list
JM 85         primaryUrl = repositoryUrls.size() == 0 ? null : repositoryUrls.get(0);
8c9255 86
1590fd 87         boolean canClone = primaryUrl != null && (!primaryUrl.hasPermission() || primaryUrl.permission.atLeast(AccessPermission.CLONE));
828add 88
JM 89         if (repositoryUrls.size() == 0 || !canClone) {
90             // no urls, nothing to show.
91             add(new Label("repositoryUrlPanel").setVisible(false));
92             add(new Label("applicationMenusPanel").setVisible(false));
6ef8d7 93             add(new Label("repositoryIndicators").setVisible(false));
ffbf03 94             return;
JM 95         }
699e71 96
828add 97         // display primary url
JM 98         add(createPrimaryUrlPanel("repositoryUrlPanel", repository, repositoryUrls));
6ef8d7 99
JM 100         if (onlyUrls) {
101             add(new Label("repositoryIndicators").setVisible(false));
102         } else {
103             add(createRepositoryIndicators(repository));
104         }
828add 105
99d0d4 106         boolean allowAppLinks = app().settings().getBoolean(Keys.web.allowAppCloneLinks, true);
828add 107         if (onlyUrls || !canClone || !allowAppLinks) {
JM 108             // only display the url(s)
109             add(new Label("applicationMenusPanel").setVisible(false));
110             return;
8c9255 111         }
828add 112         // create the git client application menus
JM 113         add(createApplicationMenus("applicationMenusPanel", user, repository, repositoryUrls));
8c9255 114     }
828add 115
8c9255 116     public String getPrimaryUrl() {
ffbf03 117         return primaryUrl == null ? "" : primaryUrl.url;
8c9255 118     }
828add 119
JM 120     protected Fragment createPrimaryUrlPanel(String wicketId, final RepositoryModel repository, List<RepositoryUrl> repositoryUrls) {
121
122         Fragment urlPanel = new Fragment(wicketId, "repositoryUrlFragment", this);
123         urlPanel.setRenderBodyOnly(true);
699e71 124
828add 125         if (repositoryUrls.size() == 1) {
JM 126             //
127             // Single repository url, no dropdown menu
128             //
129             urlPanel.add(new Label("menu").setVisible(false));
130         } else {
131             //
132             // Multiple repository urls, show url drop down menu
133             //
134             ListDataProvider<RepositoryUrl> urlsDp = new ListDataProvider<RepositoryUrl>(repositoryUrls);
135             DataView<RepositoryUrl> repoUrlMenuItems = new DataView<RepositoryUrl>("repoUrls", urlsDp) {
136                 private static final long serialVersionUID = 1L;
137
699e71 138                 @Override
828add 139                 public void populateItem(final Item<RepositoryUrl> item) {
JM 140                     RepositoryUrl repoUrl = item.getModelObject();
141                     // repository url
699e71 142                     Fragment fragment = new Fragment("repoUrl", "actionFragment", this);
828add 143                     Component content = new Label("content", repoUrl.url).setRenderBodyOnly(true);
JM 144                     WicketUtils.setCssClass(content, "commandMenuItem");
145                     fragment.add(content);
146                     item.add(fragment);
699e71 147
1590fd 148                     Label permissionLabel = new Label("permission", repoUrl.hasPermission() ? repoUrl.permission.toString() : externalPermission);
828add 149                     WicketUtils.setPermissionClass(permissionLabel, repoUrl.permission);
JM 150                     String tooltip = getProtocolPermissionDescription(repository, repoUrl);
151                     WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
152                     fragment.add(permissionLabel);
153                     fragment.add(createCopyFragment(repoUrl.url));
154                 }
155             };
156
157             Fragment urlMenuFragment = new Fragment("menu", "urlProtocolMenuFragment", this);
158             urlMenuFragment.setRenderBodyOnly(true);
159             urlMenuFragment.add(new Label("menuText", getString("gb.url")));
160             urlMenuFragment.add(repoUrlMenuItems);
161             urlPanel.add(urlMenuFragment);
8c9255 162         }
828add 163
JM 164         // access restriction icon and tooltip
e55930 165         if (repository.isMirror) {
JM 166             urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "mirror_16x16.png",
167                     getString("gb.isMirror")));
7d3a31 168         } else if (app().services().isServingRepositories()) {
828add 169             switch (repository.accessRestriction) {
JM 170             case NONE:
171                 urlPanel.add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
172                 break;
173             case PUSH:
174                 urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
175                         getAccessRestrictions().get(repository.accessRestriction)));
176                 break;
177             case CLONE:
178                 urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
179                         getAccessRestrictions().get(repository.accessRestriction)));
180                 break;
181             case VIEW:
182                 urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
183                         getAccessRestrictions().get(repository.accessRestriction)));
184                 break;
185             default:
22ad1f 186                 if (repositoryUrls.size() == 1) {
JM 187                     // force left end cap to have some width
188                     urlPanel.add(WicketUtils.newBlankIcon("accessRestrictionIcon"));
189                 } else {
190                     urlPanel.add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
191                 }
8c9255 192             }
828add 193         } else {
22ad1f 194             if (repositoryUrls.size() == 1) {
JM 195                 // force left end cap to have some width
196                 urlPanel.add(WicketUtils.newBlankIcon("accessRestrictionIcon"));
197             } else {
198                 urlPanel.add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
199             }
ffbf03 200         }
699e71 201
828add 202         urlPanel.add(new Label("primaryUrl", primaryUrl.url).setRenderBodyOnly(true));
JM 203
1590fd 204         Label permissionLabel = new Label("primaryUrlPermission", primaryUrl.hasPermission() ? primaryUrl.permission.toString() : externalPermission);
828add 205         String tooltip = getProtocolPermissionDescription(repository, primaryUrl);
JM 206         WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
207         urlPanel.add(permissionLabel);
208         urlPanel.add(createCopyFragment(primaryUrl.url));
699e71 209
828add 210         return urlPanel;
ffbf03 211     }
699e71 212
7569e8 213     protected Fragment createApplicationMenus(String wicketId, final UserModel user, final RepositoryModel repository, final List<RepositoryUrl> repositoryUrls) {
828add 214         final List<GitClientApplication> displayedApps = new ArrayList<GitClientApplication>();
JM 215         final String userAgent = ((WebClientInfo) GitBlitWebSession.get().getClientInfo()).getUserAgent();
699e71 216
828add 217         if (user.canClone(repository)) {
99d0d4 218             for (GitClientApplication app : app().gitblit().getClientApplications()) {
828add 219                 if (app.isActive && app.allowsPlatform(userAgent)) {
JM 220                     displayedApps.add(app);
8c9255 221                 }
JM 222             }
828add 223         }
JM 224
366bec 225         final String baseURL = WicketUtils.getGitblitURL(RequestCycle.get().getRequest());
828add 226         ListDataProvider<GitClientApplication> displayedAppsDp = new ListDataProvider<GitClientApplication>(displayedApps);
JM 227         DataView<GitClientApplication> appMenus = new DataView<GitClientApplication>("appMenus", displayedAppsDp) {
228             private static final long serialVersionUID = 1L;
229
699e71 230             @Override
828add 231             public void populateItem(final Item<GitClientApplication> item) {
JM 232                 final GitClientApplication clientApp = item.getModelObject();
233
366bec 234                 // filter the urls for the client app
416614 235                 List<RepositoryUrl> urls = new ArrayList<RepositoryUrl>();
JM 236                 for (RepositoryUrl repoUrl : repositoryUrls) {
1590fd 237                     if (clientApp.minimumPermission == null || !repoUrl.hasPermission()) {
JJ 238                         // no minimum permission or untracked permissions, assume it is satisfactory
416614 239                         if (clientApp.supportsTransport(repoUrl.url)) {
366bec 240                             urls.add(repoUrl);
416614 241                         }
JM 242                     } else if (repoUrl.permission.atLeast(clientApp.minimumPermission)) {
243                         // repo url meets minimum permission requirement
244                         if (clientApp.supportsTransport(repoUrl.url)) {
366bec 245                             urls.add(repoUrl);
JM 246                         }
247                     }
248                 }
699e71 249
366bec 250                 if (urls.size() == 0) {
JM 251                     // do not show this app menu because there are no urls
252                     item.add(new Label("appMenu").setVisible(false));
253                     return;
254                 }
699e71 255
366bec 256                 Fragment appMenu = new Fragment("appMenu", "appMenuFragment", this);
JM 257                 appMenu.setRenderBodyOnly(true);
258                 item.add(appMenu);
699e71 259
828add 260                 // menu button
366bec 261                 appMenu.add(new Label("applicationName", clientApp.name));
699e71 262
828add 263                 // application icon
JM 264                 Component img;
265                 if (StringUtils.isEmpty(clientApp.icon)) {
699e71 266                     img = WicketUtils.newClearPixel("applicationIcon").setVisible(false);
828add 267                 } else {
366bec 268                     if (clientApp.icon.contains("://")) {
JM 269                         // external image
270                         img = new ExternalImage("applicationIcon", clientApp.icon);
271                     } else {
272                         // context image
273                         img = WicketUtils.newImage("applicationIcon", clientApp.icon);
274                     }
699e71 275                 }
366bec 276                 appMenu.add(img);
699e71 277
828add 278                 // application menu title, may be a link
JM 279                 if (StringUtils.isEmpty(clientApp.productUrl)) {
366bec 280                     appMenu.add(new Label("applicationTitle", clientApp.toString()));
828add 281                 } else {
366bec 282                     appMenu.add(new LinkPanel("applicationTitle", null, clientApp.toString(), clientApp.productUrl, true));
828add 283                 }
699e71 284
828add 285                 // brief application description
JM 286                 if (StringUtils.isEmpty(clientApp.description)) {
366bec 287                     appMenu.add(new Label("applicationDescription").setVisible(false));
828add 288                 } else {
366bec 289                     appMenu.add(new Label("applicationDescription", clientApp.description));
828add 290                 }
699e71 291
828add 292                 // brief application legal info, copyright, license, etc
JM 293                 if (StringUtils.isEmpty(clientApp.legal)) {
366bec 294                     appMenu.add(new Label("applicationLegal").setVisible(false));
828add 295                 } else {
366bec 296                     appMenu.add(new Label("applicationLegal", clientApp.legal));
828add 297                 }
699e71 298
828add 299                 // a nested repeater for all action items
366bec 300                 ListDataProvider<RepositoryUrl> urlsDp = new ListDataProvider<RepositoryUrl>(urls);
828add 301                 DataView<RepositoryUrl> actionItems = new DataView<RepositoryUrl>("actionItems", urlsDp) {
JM 302                     private static final long serialVersionUID = 1L;
303
699e71 304                     @Override
828add 305                     public void populateItem(final Item<RepositoryUrl> repoLinkItem) {
JM 306                         RepositoryUrl repoUrl = repoLinkItem.getModelObject();
307                         Fragment fragment = new Fragment("actionItem", "actionFragment", this);
308                         fragment.add(createPermissionBadge("permission", repoUrl));
309
310                         if (!StringUtils.isEmpty(clientApp.cloneUrl)) {
311                             // custom registered url
7569e8 312                             String url = substitute(clientApp.cloneUrl, repoUrl.url, baseURL, user.username, repository.name);
828add 313                             fragment.add(new LinkPanel("content", "applicationMenuItem", getString("gb.clone") + " " + repoUrl.url, url));
JM 314                             repoLinkItem.add(fragment);
315                             fragment.add(new Label("copyFunction").setVisible(false));
316                         } else if (!StringUtils.isEmpty(clientApp.command)) {
317                             // command-line
7569e8 318                             String command = substitute(clientApp.command, repoUrl.url, baseURL, user.username, repository.name);
828add 319                             Label content = new Label("content", command);
JM 320                             WicketUtils.setCssClass(content, "commandMenuItem");
321                             fragment.add(content);
322                             repoLinkItem.add(fragment);
699e71 323
828add 324                             // copy function for command
JM 325                             fragment.add(createCopyFragment(command));
326                         }
327                     }};
366bec 328                     appMenu.add(actionItems);
828add 329             }
JM 330         };
699e71 331
828add 332         Fragment applicationMenus = new Fragment(wicketId, "applicationMenusFragment", this);
JM 333         applicationMenus.add(appMenus);
334         return applicationMenus;
335     }
699e71 336
7569e8 337     protected String substitute(String pattern, String repoUrl, String baseUrl, String username, String repository) {
JM 338         return pattern.replace("${repoUrl}", repoUrl).replace("${baseUrl}", baseUrl).replace("${username}", username).replace("${repository}", repository);
828add 339     }
699e71 340
828add 341     protected Label createPermissionBadge(String wicketId, RepositoryUrl repoUrl) {
1590fd 342         Label permissionLabel = new Label(wicketId, repoUrl.hasPermission() ? repoUrl.permission.toString() : externalPermission);
828add 343         WicketUtils.setPermissionClass(permissionLabel, repoUrl.permission);
JM 344         String tooltip = getProtocolPermissionDescription(repository, repoUrl);
345         WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
346         return permissionLabel;
347     }
699e71 348
828add 349     protected Fragment createCopyFragment(String text) {
99d0d4 350         if (app().settings().getBoolean(Keys.web.allowFlashCopyToClipboard, true)) {
828add 351             // clippy: flash-based copy & paste
JM 352             Fragment copyFragment = new Fragment("copyFunction", "clippyPanel", this);
353             String baseUrl = WicketUtils.getGitblitURL(getRequest());
354             ShockWaveComponent clippy = new ShockWaveComponent("clippy", baseUrl + "/clippy.swf");
355             clippy.setValue("flashVars", "text=" + StringUtils.encodeURL(text));
356             copyFragment.add(clippy);
357             return copyFragment;
ffbf03 358         } else {
828add 359             // javascript: manual copy & paste with modal browser prompt dialog
JM 360             Fragment copyFragment = new Fragment("copyFunction", "jsPanel", this);
361             ContextImage img = WicketUtils.newImage("copyIcon", "clippy.png");
362             img.add(new JavascriptTextPrompt("onclick", "Copy to Clipboard (Ctrl+C, Enter)", text));
363             copyFragment.add(img);
364             return copyFragment;
365         }
366     }
699e71 367
828add 368     protected String getProtocolPermissionDescription(RepositoryModel repository,
JM 369             RepositoryUrl repoUrl) {
370         if (!urlPermissionsMap.containsKey(repoUrl.url)) {
371             String note;
1590fd 372             if (repoUrl.hasPermission()) {
699e71 373                 note = null;
828add 374                 String key;
JM 375                 switch (repoUrl.permission) {
ffbf03 376                 case OWNER:
JM 377                 case REWIND:
378                     key = "gb.rewindPermission";
379                     break;
380                 case DELETE:
381                     key = "gb.deletePermission";
382                     break;
383                 case CREATE:
384                     key = "gb.createPermission";
385                     break;
386                 case PUSH:
387                     key = "gb.pushPermission";
388                     break;
389                 case CLONE:
390                     key = "gb.clonePermission";
391                     break;
392                 default:
393                     key = null;
394                     note = getString("gb.viewAccess");
395                     break;
828add 396                 }
JM 397
398                 if (note == null) {
399                     String pattern = getString(key);
400                     String description = MessageFormat.format(pattern, repoUrl.permission.toString());
401                     note = description;
402                 }
1590fd 403             } else {
JJ 404                 String protocol;
405                 int protocolIndex = repoUrl.url.indexOf("://");
406                 if (protocolIndex > -1) {
407                     // explicit protocol specified
408                     protocol = repoUrl.url.substring(0, protocolIndex);
409                 } else {
410                     // implicit SSH url
411                     protocol = "ssh";
412                 }
413                 note = MessageFormat.format(getString("gb.externalPermissions"), protocol);
ffbf03 414             }
828add 415             urlPermissionsMap.put(repoUrl.url, note);
ffbf03 416         }
828add 417         return urlPermissionsMap.get(repoUrl.url);
ffbf03 418     }
699e71 419
828add 420     protected Map<AccessRestrictionType, String> getAccessRestrictions() {
JM 421         if (accessRestrictionsMap == null) {
422             accessRestrictionsMap = new HashMap<AccessRestrictionType, String>();
423             for (AccessRestrictionType type : AccessRestrictionType.values()) {
424                 switch (type) {
425                 case NONE:
426                     accessRestrictionsMap.put(type, getString("gb.notRestricted"));
427                     break;
428                 case PUSH:
429                     accessRestrictionsMap.put(type, getString("gb.pushRestricted"));
430                     break;
431                 case CLONE:
432                     accessRestrictionsMap.put(type, getString("gb.cloneRestricted"));
433                     break;
434                 case VIEW:
435                     accessRestrictionsMap.put(type, getString("gb.viewRestricted"));
436                     break;
437                 }
438             }
8c9255 439         }
828add 440         return accessRestrictionsMap;
82df52 441     }
699e71 442
6ef8d7 443     protected Component createRepositoryIndicators(RepositoryModel repository) {
JM 444         Fragment fragment = new Fragment("repositoryIndicators", "indicatorsFragment", this);
445         if (repository.isBare) {
446             fragment.add(new Label("workingCopyIndicator").setVisible(false));
447         } else {
448             Fragment wc = new Fragment("workingCopyIndicator", "workingCopyFragment", this);
449             Label lbl = new Label("workingCopy", getString("gb.workingCopy"));
450             WicketUtils.setHtmlTooltip(lbl,  getString("gb.workingCopyWarning"));
451             wc.add(lbl);
452             fragment.add(wc);
453         }
699e71 454
99d0d4 455         boolean allowForking = app().settings().getBoolean(Keys.web.allowForking, true);
6ef8d7 456         if (!allowForking || user == null || !user.isAuthenticated) {
JM 457             // must be logged-in to fork, hide all fork controls
458             fragment.add(new Label("forksProhibitedIndicator").setVisible(false));
459         } else {
99d0d4 460             String fork = app().repositories().getFork(user.username, repository.name);
6ef8d7 461             boolean hasFork = fork != null;
JM 462             boolean canFork = user.canFork(repository);
463
464             if (hasFork || !canFork) {
465                 if (user.canFork() && !repository.allowForks) {
466                     // show forks prohibited indicator
467                     Fragment wc = new Fragment("forksProhibitedIndicator", "forksProhibitedFragment", this);
468                     Label lbl = new Label("forksProhibited", getString("gb.forksProhibited"));
469                     WicketUtils.setHtmlTooltip(lbl,  getString("gb.forksProhibitedWarning"));
470                     wc.add(lbl);
471                     fragment.add(wc);
472                 } else {
473                     // can not fork, no need for forks prohibited indicator
474                     fragment.add(new Label("forksProhibitedIndicator").setVisible(false));
475                 }
476             } else if (canFork) {
477                 // can fork and we do not have one
478                 fragment.add(new Label("forksProhibitedIndicator").setVisible(false));
479             }
480         }
481         return fragment;
482     }
82df52 483 }