public.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. /*
  2. * Copyright (c) 2014
  3. * @copyright Copyright (c) 2016, Björn Schießle <bjoern@schiessle.org>
  4. *
  5. * This file is licensed under the Affero General Public License version 3
  6. * or later.
  7. *
  8. * See the COPYING-README file.
  9. *
  10. */
  11. /* global FileActions, Files, FileList */
  12. /* global dragOptions, folderDropOptions */
  13. if (!OCA.Sharing) {
  14. OCA.Sharing = {};
  15. }
  16. if (!OCA.Files) {
  17. OCA.Files = {};
  18. }
  19. /**
  20. * @namespace
  21. */
  22. OCA.Sharing.PublicApp = {
  23. _initialized: false,
  24. /**
  25. * Initializes the public share app.
  26. *
  27. * @param $el container
  28. */
  29. initialize: function ($el) {
  30. var self = this;
  31. var fileActions;
  32. if (this._initialized) {
  33. return;
  34. }
  35. fileActions = new OCA.Files.FileActions();
  36. // default actions
  37. fileActions.registerDefaultActions();
  38. // regular actions
  39. fileActions.merge(OCA.Files.fileActions);
  40. // in case apps would decide to register file actions later,
  41. // replace the global object with this one
  42. OCA.Files.fileActions = fileActions;
  43. this._initialized = true;
  44. this.initialDir = $('#dir').val();
  45. var token = $('#sharingToken').val();
  46. var hideDownload = $('#hideDownload').val();
  47. // file list mode ?
  48. if ($el.find('#filestable').length) {
  49. var filesClient = new OC.Files.Client({
  50. host: OC.getHost(),
  51. port: OC.getPort(),
  52. userName: token,
  53. // note: password not be required, the endpoint
  54. // will recognize previous validation from the session
  55. root: OC.getRootPath() + '/public.php/webdav',
  56. useHTTPS: OC.getProtocol() === 'https'
  57. });
  58. this.fileList = new OCA.Files.FileList(
  59. $el,
  60. {
  61. id: 'files.public',
  62. dragOptions: dragOptions,
  63. folderDropOptions: folderDropOptions,
  64. fileActions: fileActions,
  65. detailsViewEnabled: false,
  66. filesClient: filesClient,
  67. enableUpload: true,
  68. multiSelectMenu: [
  69. {
  70. name: 'copyMove',
  71. displayName: t('files', 'Move or copy'),
  72. iconClass: 'icon-external',
  73. },
  74. {
  75. name: 'download',
  76. displayName: t('files', 'Download'),
  77. iconClass: 'icon-download',
  78. },
  79. {
  80. name: 'delete',
  81. displayName: t('files', 'Delete'),
  82. iconClass: 'icon-delete',
  83. }
  84. ]
  85. }
  86. );
  87. if (hideDownload === 'true') {
  88. this.fileList._allowSelection = false;
  89. }
  90. this.files = OCA.Files.Files;
  91. this.files.initialize();
  92. // TODO: move to PublicFileList.initialize() once
  93. // the code was split into a separate class
  94. OC.Plugins.attach('OCA.Sharing.PublicFileList', this.fileList);
  95. }
  96. var mimetype = $('#mimetype').val();
  97. var mimetypeIcon = $('#mimetypeIcon').val();
  98. mimetypeIcon = mimetypeIcon.substring(0, mimetypeIcon.length - 3);
  99. mimetypeIcon = mimetypeIcon + 'svg';
  100. var previewSupported = $('#previewSupported').val();
  101. if (typeof FileActions !== 'undefined') {
  102. // Show file preview if previewer is available, images are already handled by the template
  103. if (mimetype.substr(0, mimetype.indexOf('/')) !== 'image' && $('.publicpreview').length === 0) {
  104. // Trigger default action if not download TODO
  105. var spec = FileActions.getDefaultFileAction(mimetype, 'file', OC.PERMISSION_READ);
  106. if (spec && spec.action) {
  107. spec.action($('#filename').val());
  108. }
  109. }
  110. }
  111. // dynamically load image previews
  112. var bottomMargin = 350;
  113. var previewWidth = $(window).width();
  114. var previewHeight = $(window).height() - bottomMargin;
  115. previewHeight = Math.max(200, previewHeight);
  116. var params = {
  117. x: Math.ceil(previewWidth * window.devicePixelRatio),
  118. y: Math.ceil(previewHeight * window.devicePixelRatio),
  119. a: 'true',
  120. file: encodeURIComponent(this.initialDir + $('#filename').val()),
  121. scalingup: 0
  122. };
  123. var imgcontainer = $('<img class="publicpreview" alt="">');
  124. if (hideDownload === 'false') {
  125. imgcontainer = $('<a href="' + $('#previewURL').val() + '" target="_blank"></a>').append(imgcontainer);
  126. }
  127. var img = imgcontainer.hasClass('publicpreview')? imgcontainer: imgcontainer.find('.publicpreview');
  128. img.css({
  129. 'max-width': previewWidth,
  130. 'max-height': previewHeight
  131. });
  132. var fileSize = parseInt($('#filesize').val(), 10);
  133. var maxGifSize = parseInt($('#maxSizeAnimateGif').val(), 10);
  134. if (mimetype === 'image/gif' &&
  135. (maxGifSize === -1 || fileSize <= (maxGifSize * 1024 * 1024))) {
  136. img.attr('src', $('#downloadURL').val());
  137. imgcontainer.appendTo('#imgframe');
  138. } else if (mimetype.substr(0, mimetype.indexOf('/')) === 'text' && window.btoa) {
  139. if (OC.appswebroots['files_texteditor'] !== undefined ||
  140. OC.appswebroots['text'] !== undefined) {
  141. // the text editor handles the previewing
  142. return;
  143. }
  144. // Undocumented Url to public WebDAV endpoint
  145. var url = parent.location.protocol + '//' + location.host + OC.linkTo('', 'public.php/webdav');
  146. $.ajax({
  147. url: url,
  148. headers: {
  149. Authorization: 'Basic ' + btoa(token + ':'),
  150. Range: 'bytes=0-10000'
  151. }
  152. }).then(function (data) {
  153. self._showTextPreview(data, previewHeight);
  154. });
  155. } else if ((previewSupported === 'true' && mimetype.substr(0, mimetype.indexOf('/')) !== 'video') ||
  156. mimetype.substr(0, mimetype.indexOf('/')) === 'image' &&
  157. mimetype !== 'image/svg+xml') {
  158. img.attr('src', OC.generateUrl('/apps/files_sharing/publicpreview/' + token + '?' + OC.buildQueryString(params)));
  159. imgcontainer.appendTo('#imgframe');
  160. } else if (mimetype.substr(0, mimetype.indexOf('/')) !== 'video') {
  161. img.attr('src', mimetypeIcon);
  162. img.attr('width', 128);
  163. // "#imgframe" is either empty or it contains an audio preview that
  164. // the icon should appear before, so the container should be
  165. // prepended to the frame.
  166. imgcontainer.prependTo('#imgframe');
  167. }
  168. else if (previewSupported === 'true') {
  169. $('#imgframe > video').attr('poster', OC.generateUrl('/apps/files_sharing/publicpreview/' + token + '?' + OC.buildQueryString(params)));
  170. }
  171. if (this.fileList) {
  172. // TODO: move this to a separate PublicFileList class that extends OCA.Files.FileList (+ unit tests)
  173. this.fileList.getDownloadUrl = function (filename, dir, isDir) {
  174. var path = dir || this.getCurrentDirectory();
  175. if (_.isArray(filename)) {
  176. filename = JSON.stringify(filename);
  177. }
  178. var params = {
  179. path: path
  180. };
  181. if (filename) {
  182. params.files = filename;
  183. }
  184. return OC.generateUrl('/s/' + token + '/download') + '?' + OC.buildQueryString(params);
  185. };
  186. this.fileList._createRow = function(fileData) {
  187. var $tr = OCA.Files.FileList.prototype._createRow.apply(this, arguments);
  188. if (hideDownload === 'true') {
  189. this.fileActions.currentFile = $tr.find('td');
  190. // Remove the link. This means that files without a default action fail hard
  191. $tr.find('a.name').attr('href', '#');
  192. this.fileActions.actions.all = {};
  193. }
  194. return $tr;
  195. };
  196. this.fileList.isSelectedDownloadable = function () {
  197. return hideDownload !== 'true';
  198. };
  199. this.fileList.getUploadUrl = function(fileName, dir) {
  200. if (_.isUndefined(dir)) {
  201. dir = this.getCurrentDirectory();
  202. }
  203. var pathSections = dir.split('/');
  204. if (!_.isUndefined(fileName)) {
  205. pathSections.push(fileName);
  206. }
  207. var encodedPath = '';
  208. _.each(pathSections, function(section) {
  209. if (section !== '') {
  210. encodedPath += '/' + encodeURIComponent(section);
  211. }
  212. });
  213. var base = '';
  214. if (!this._uploader.isXHRUpload()) {
  215. // also add auth in URL due to POST workaround
  216. base = OC.getProtocol() + '://' + token + '@' + OC.getHost() + (OC.getPort() ? ':' + OC.getPort() : '');
  217. }
  218. return base + OC.getRootPath() + '/public.php/webdav' + encodedPath;
  219. };
  220. this.fileList.getAjaxUrl = function (action, params) {
  221. params = params || {};
  222. params.t = token;
  223. return OC.filePath('files_sharing', 'ajax', action + '.php') + '?' + OC.buildQueryString(params);
  224. };
  225. this.fileList.linkTo = function (dir) {
  226. return OC.generateUrl('/s/' + token + '') + '?' + OC.buildQueryString({path: dir});
  227. };
  228. this.fileList.generatePreviewUrl = function (urlSpec) {
  229. urlSpec = urlSpec || {};
  230. if (!urlSpec.x) {
  231. urlSpec.x = this.$table.data('preview-x') || 250;
  232. }
  233. if (!urlSpec.y) {
  234. urlSpec.y = this.$table.data('preview-y') || 250;
  235. }
  236. urlSpec.x *= window.devicePixelRatio;
  237. urlSpec.y *= window.devicePixelRatio;
  238. urlSpec.x = Math.ceil(urlSpec.x);
  239. urlSpec.y = Math.ceil(urlSpec.y);
  240. var token = $('#dirToken').val();
  241. return OC.generateUrl('/apps/files_sharing/publicpreview/' + token + '?' + OC.buildQueryString(urlSpec));
  242. };
  243. this.fileList.updateEmptyContent = function() {
  244. this.$el.find('#emptycontent .uploadmessage').text(
  245. t('files_sharing', 'You can upload into this folder')
  246. );
  247. OCA.Files.FileList.prototype.updateEmptyContent.apply(this, arguments);
  248. };
  249. this.fileList._uploader.on('fileuploadadd', function(e, data) {
  250. if (!data.headers) {
  251. data.headers = {};
  252. }
  253. data.headers.Authorization = 'Basic ' + btoa(token + ':');
  254. });
  255. // do not allow sharing from the public page
  256. delete this.fileList.fileActions.actions.all.Share;
  257. this.fileList.changeDirectory(this.initialDir || '/', false, true);
  258. // URL history handling
  259. this.fileList.$el.on('changeDirectory', _.bind(this._onDirectoryChanged, this));
  260. OC.Util.History.addOnPopStateHandler(_.bind(this._onUrlChanged, this));
  261. $('#download').click(function (e) {
  262. e.preventDefault();
  263. OC.redirect(FileList.getDownloadUrl());
  264. });
  265. if (hideDownload === 'true') {
  266. this.fileList.$el.find('#headerSelection').remove();
  267. this.fileList.$el.find('.summary').find('td:first-child').remove();
  268. }
  269. }
  270. $(document).on('click', '#directLink', function () {
  271. $(this).focus();
  272. $(this).select();
  273. });
  274. $('.save-form').submit(function (event) {
  275. event.preventDefault();
  276. var remote = $(this).find('#remote_address').val();
  277. var token = $('#sharingToken').val();
  278. var owner = $('#save-external-share').data('owner');
  279. var ownerDisplayName = $('#save-external-share').data('owner-display-name');
  280. var name = $('#save-external-share').data('name');
  281. var isProtected = $('#save-external-share').data('protected') ? 1 : 0;
  282. OCA.Sharing.PublicApp._createFederatedShare(remote, token, owner, ownerDisplayName, name, isProtected);
  283. });
  284. $('#remote_address').on("keyup paste", function() {
  285. if ($(this).val() === '' || $('#save-external-share > .icon.icon-loading-small').length > 0) {
  286. $('#save-button-confirm').prop('disabled', true);
  287. } else {
  288. $('#save-button-confirm').prop('disabled', false);
  289. }
  290. });
  291. self._bindShowTermsAction();
  292. // legacy
  293. window.FileList = this.fileList;
  294. },
  295. /**
  296. * Binds the click action for the "terms of service" action.
  297. * Shows an OC info dialog on click.
  298. *
  299. * @private
  300. */
  301. _bindShowTermsAction: function() {
  302. $('#show-terms-dialog').on('click', function() {
  303. OC.dialogs.info($('#disclaimerText').val(), t('files_sharing', 'Terms of service'));
  304. });
  305. },
  306. _showTextPreview: function (data, previewHeight) {
  307. var textDiv = $('<div/>').addClass('text-preview');
  308. textDiv.text(data);
  309. textDiv.appendTo('#imgframe');
  310. var divHeight = textDiv.height();
  311. if (data.length > 999) {
  312. var ellipsis = $('<div/>').addClass('ellipsis');
  313. ellipsis.html('(&#133;)');
  314. ellipsis.appendTo('#imgframe');
  315. }
  316. if (divHeight > previewHeight) {
  317. textDiv.height(previewHeight);
  318. }
  319. },
  320. _onDirectoryChanged: function (e) {
  321. OC.Util.History.pushState({
  322. // arghhhh, why is this not called "dir" !?
  323. path: e.dir
  324. });
  325. },
  326. _onUrlChanged: function (params) {
  327. this.fileList.changeDirectory(params.path || params.dir, false, true);
  328. },
  329. /**
  330. * fall back to old behaviour where we redirect the user to his server to mount
  331. * the public link instead of creating a dedicated federated share
  332. *
  333. * @param remote
  334. * @param token
  335. * @param owner
  336. * @param ownerDisplayName
  337. * @param name
  338. * @param isProtected
  339. * @private
  340. */
  341. _legacyCreateFederatedShare: function (remote, token, owner, ownerDisplayName, name, isProtected) {
  342. var self = this;
  343. var location = window.location.protocol + '//' + window.location.host + OC.getRootPath();
  344. if(remote.substr(-1) !== '/') {
  345. remote += '/'
  346. }
  347. var url = remote + 'index.php/apps/files#' + 'remote=' + encodeURIComponent(location) // our location is the remote for the other server
  348. + "&token=" + encodeURIComponent(token) + "&owner=" + encodeURIComponent(owner) +"&ownerDisplayName=" + encodeURIComponent(ownerDisplayName) + "&name=" + encodeURIComponent(name) + "&protected=" + isProtected;
  349. if (remote.indexOf('://') > 0) {
  350. OC.redirect(url);
  351. } else {
  352. // if no protocol is specified, we automatically detect it by testing https and http
  353. // this check needs to happen on the server due to the Content Security Policy directive
  354. $.get(OC.generateUrl('apps/files_sharing/testremote'), {remote: remote}).then(function (protocol) {
  355. if (protocol !== 'http' && protocol !== 'https') {
  356. self._toggleLoading();
  357. OC.dialogs.alert(t('files_sharing', 'No compatible server found at {remote}', {remote: remote}),
  358. t('files_sharing', 'Invalid server URL'));
  359. } else {
  360. OC.redirect(protocol + '://' + url);
  361. }
  362. });
  363. }
  364. },
  365. _toggleLoading: function() {
  366. var loading = $('#save-external-share > .icon.icon-loading-small').length === 0;
  367. if (loading) {
  368. $('#save-external-share > .icon-external')
  369. .removeClass("icon-external")
  370. .addClass("icon-loading-small");
  371. $('#save-external-share #save-button-confirm').prop("disabled", true);
  372. } else {
  373. $('#save-external-share > .icon-loading-small')
  374. .addClass("icon-external")
  375. .removeClass("icon-loading-small");
  376. $('#save-external-share #save-button-confirm').prop("disabled", false);
  377. }
  378. },
  379. _createFederatedShare: function (remote, token, owner, ownerDisplayName, name, isProtected) {
  380. var self = this;
  381. this._toggleLoading();
  382. if (remote.indexOf('@') === -1) {
  383. this._legacyCreateFederatedShare(remote, token, owner, ownerDisplayName, name, isProtected);
  384. return;
  385. }
  386. $.post(
  387. OC.generateUrl('/apps/federatedfilesharing/createFederatedShare'),
  388. {
  389. 'shareWith': remote,
  390. 'token': token
  391. }
  392. ).done(
  393. function (data) {
  394. var url = data.remoteUrl;
  395. if (url.indexOf('://') > 0) {
  396. OC.redirect(url);
  397. } else {
  398. OC.redirect('http://' + url);
  399. }
  400. }
  401. ).fail(
  402. function (jqXHR) {
  403. OC.dialogs.alert(JSON.parse(jqXHR.responseText).message,
  404. t('files_sharing', 'Failed to add the public link to your Nextcloud'));
  405. self._toggleLoading();
  406. }
  407. );
  408. }
  409. };
  410. window.addEventListener('DOMContentLoaded', function () {
  411. // FIXME: replace with OC.Plugins.register()
  412. if (window.TESTING) {
  413. return;
  414. }
  415. var App = OCA.Sharing.PublicApp;
  416. // defer app init, to give a chance to plugins to register file actions
  417. _.defer(function () {
  418. App.initialize($('#preview'));
  419. });
  420. if (window.Files) {
  421. // HACK: for oc-dialogs previews that depends on Files:
  422. Files.generatePreviewUrl = function (urlSpec) {
  423. return App.fileList.generatePreviewUrl(urlSpec);
  424. };
  425. }
  426. });