diff --git a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee index 951506891f..d7e9ed4bc6 100644 --- a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee +++ b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee @@ -84,7 +84,7 @@ module.exports = AuthenticationController = doPassportLogin: (req, username, password, done) -> email = username.toLowerCase() Modules = require "../../infrastructure/Modules" - Modules.hooks.fire 'preDoPassportLogin', email, (err, infoList) -> + Modules.hooks.fire 'preDoPassportLogin', req, email, (err, infoList) -> return next(err) if err? info = infoList.find((i) => i?) if info? diff --git a/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee b/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee index 2bee2ba5e6..f2c982a077 100644 --- a/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee +++ b/services/web/app/coffee/Features/Compile/ClsiCookieManager.coffee @@ -49,7 +49,7 @@ module.exports = (backendGroup)-> return callback() serverId = @_parseServerIdFromResponse(response) if !serverId? # We don't get a cookie back if it hasn't changed - return callback() + return rclient.expire(@buildKey(project_id), Settings.clsiCookie.ttl, callback) if rclient_secondary? @_setServerIdInRedis rclient_secondary, project_id, serverId @_setServerIdInRedis rclient, project_id, serverId, (err) -> diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee index 8ce39d68e3..c62a8a01bc 100644 --- a/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee +++ b/services/web/app/coffee/Features/Institutions/InstitutionsAPI.coffee @@ -80,6 +80,8 @@ makeAffiliationRequest = (requestOptions, callback = (error) ->) -> errorMessage = "#{response.statusCode}: #{body.errors}" else errorMessage = "#{requestOptions.defaultErrorMessage}: #{response.statusCode}" + + logger.err path: requestOptions.path, body: requestOptions.body, errorMessage return callback(new Error(errorMessage)) callback(null, body) diff --git a/services/web/app/coffee/Features/User/UserCreator.coffee b/services/web/app/coffee/Features/User/UserCreator.coffee index 2dfa7f5ac0..17248b38ae 100644 --- a/services/web/app/coffee/Features/User/UserCreator.coffee +++ b/services/web/app/coffee/Features/User/UserCreator.coffee @@ -6,15 +6,18 @@ metrics = require('metrics-sharelatex') module.exports = UserCreator = - createNewUser: (opts, callback)-> - logger.log opts:opts, "creating new user" + createNewUser: (attributes, options, callback = (error, user) ->)-> + if arguments.length == 2 + callback = options + options = {} + logger.log user: attributes, "creating new user" user = new User() - username = opts.email.match(/^[^@]*/) - if !opts.first_name? or opts.first_name == "" - opts.first_name = username[0] + username = attributes.email.match(/^[^@]*/) + if !attributes.first_name? or attributes.first_name == "" + attributes.first_name = username[0] - for key, value of opts + for key, value of attributes user[key] = value user.ace.syntaxValidation = true @@ -27,6 +30,7 @@ module.exports = UserCreator = user.save (err)-> callback(err, user) + return if options?.skip_affiliation # call addaffiliation after the main callback so it runs in the # background. There is no guaranty this will run so we must no rely on it addAffiliation user._id, user.email, (error) -> diff --git a/services/web/app/views/subscriptions/plans.pug b/services/web/app/views/subscriptions/plans.pug index 0a37b90dd8..51efcd5125 100644 --- a/services/web/app/views/subscriptions/plans.pug +++ b/services/web/app/views/subscriptions/plans.pug @@ -14,5 +14,5 @@ block content .content.plans(ng-controller="PlansController") .container(class="more-details" ng-cloak ng-if="plansVariant === 'more-details'") include _plans_page_details_more - .container(ng-cloak ng-if="plansVariant === 'default' || !shouldABTestPlans") + .container(ng-cloak ng-if="plansVariant === 'default' || !shouldABTestPlans || timeout") include _plans_page_details_less diff --git a/services/web/app/views/user/settings.pug b/services/web/app/views/user/settings.pug index e4da83c1db..d231047d8d 100644 --- a/services/web/app/views/user/settings.pug +++ b/services/web/app/views/user/settings.pug @@ -167,7 +167,12 @@ block content ) i.fa.fa-check | #{translate("unsubscribed")} - + + if !settings.overleaf && user.overleaf + p + | Please note: If you have linked your account with Overleaf + | v2, then deleting your ShareLaTeX account will also delete + | account and all of it's associated projects and data. p #{translate("need_to_leave")} a(href, ng-click="deleteAccount()") #{translate("delete_your_account")} diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 832c3855b5..59d6e0aa89 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -135,6 +135,7 @@ module.exports = settings = url: "http://#{process.env['FILESTORE_HOST'] or 'localhost'}:3009" clsi: url: "http://#{process.env['CLSI_HOST'] or 'localhost'}:3013" + # url: "http://#{process.env['CLSI_LB_HOST']}:3014" backendGroupName: undefined templates: url: "http://#{process.env['TEMPLATES_HOST'] or 'localhost'}:3007" @@ -340,7 +341,7 @@ module.exports = settings = # disablePerUserCompiles: true # Domain the client (pdfjs) should download the compiled pdf from - # pdfDownloadDomain: "http://compiles.sharelatex.test:3014" + # pdfDownloadDomain: "http://clsi-lb:3014" # Maximum size of text documents in the real-time editing system. max_doc_length: 2 * 1024 * 1024 # 2mb diff --git a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee index c717ef6b82..40264e1be0 100644 --- a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee +++ b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee @@ -225,7 +225,8 @@ define [ }, {params: params} buildPdfDownloadUrl = (pdfDownloadDomain, path)-> - if pdfDownloadDomain? + #we only download builds from compiles server for security reasons + if pdfDownloadDomain? and path.indexOf("build") != -1 return "#{pdfDownloadDomain}#{path}" else return path @@ -378,14 +379,15 @@ define [ compileGroup:ide.compileGroup clsiserverid:ide.clsiServerId if file?.url? # FIXME clean this up when we have file.urls out consistently - opts.url = buildPdfDownloadUrl options.pdfDownloadDomain, file.url + opts.url = file.url else if file?.build? - opts.url = buildPdfDownloadUrl options.pdfDownloadDomain, "/project/#{$scope.project_id}/build/#{file.build}/output/#{name}" + opts.url = "/project/#{$scope.project_id}/build/#{file.build}/output/#{name}" else - opts.url = buildPdfDownloadUrl options.pdfDownloadDomain, "/project/#{$scope.project_id}/output/#{name}" + opts.url = "/project/#{$scope.project_id}/output/#{name}" # check if we need to bust cache (build id is unique so don't need it in that case) if not file?.build? opts.params.cache_bust = "#{Date.now()}" + opts.url = buildPdfDownloadUrl options.pdfDownloadDomain, opts.url return $http(opts) # accumulate the log entries diff --git a/services/web/public/coffee/main/account-settings.coffee b/services/web/public/coffee/main/account-settings.coffee index 82d323110e..cac35394a1 100644 --- a/services/web/public/coffee/main/account-settings.coffee +++ b/services/web/public/coffee/main/account-settings.coffee @@ -1,7 +1,7 @@ define [ "base" ], (App) -> - App.controller "AccountSettingsController", ["$scope", "$http", "$modal", "event_tracking", ($scope, $http, $modal, event_tracking) -> + App.controller "AccountSettingsController", ["$scope", "$http", "$modal", "event_tracking", "UserAffiliationsDataService", ($scope, $http, $modal, event_tracking, UserAffiliationsDataService) -> $scope.subscribed = true $scope.unsubscribe = () -> @@ -21,8 +21,14 @@ define [ $scope.deleteAccount = () -> modalInstance = $modal.open( templateUrl: "deleteAccountModalTemplate" - controller: "DeleteAccountModalController", - scope: $scope + controller: "DeleteAccountModalController" + resolve: + userDefaultEmail: () -> + UserAffiliationsDataService + .getUserDefaultEmail() + .then (defaultEmailDetails) -> + return defaultEmailDetails?.email or null + .catch () -> null ) $scope.upgradeIntegration = (service) -> @@ -30,8 +36,8 @@ define [ ] App.controller "DeleteAccountModalController", [ - "$scope", "$modalInstance", "$timeout", "$http", - ($scope, $modalInstance, $timeout, $http) -> + "$scope", "$modalInstance", "$timeout", "$http", "userDefaultEmail", + ($scope, $modalInstance, $timeout, $http, userDefaultEmail) -> $scope.state = isValid : false deleteText: "" @@ -46,7 +52,7 @@ define [ , 700 $scope.checkValidation = -> - $scope.state.isValid = $scope.state.deleteText == $scope.email and $scope.state.password.length > 0 + $scope.state.isValid = userDefaultEmail? and $scope.state.deleteText == userDefaultEmail and $scope.state.password.length > 0 $scope.delete = () -> $scope.state.inflight = true diff --git a/services/web/public/coffee/main/affiliations/factories/UserAffiliationsDataService.coffee b/services/web/public/coffee/main/affiliations/factories/UserAffiliationsDataService.coffee index cbcadf7e67..ca1ffef168 100644 --- a/services/web/public/coffee/main/affiliations/factories/UserAffiliationsDataService.coffee +++ b/services/web/public/coffee/main/affiliations/factories/UserAffiliationsDataService.coffee @@ -31,6 +31,10 @@ define [ $http.get "/user/emails" .then (response) -> response.data + getUserDefaultEmail = () -> + getUserEmails().then (userEmails) -> + _.find userEmails, (userEmail) -> userEmail.default + getUniversitiesFromCountry = (country) -> if universities[country.code]? universitiesFromCountry = universities[country.code] @@ -118,6 +122,7 @@ define [ getDefaultRoleHints getDefaultDepartmentHints getUserEmails + getUserDefaultEmail getUniversitiesFromCountry getUniversityDomainFromPartialDomainInput getUniversityDetails diff --git a/services/web/public/coffee/main/new-subscription.coffee b/services/web/public/coffee/main/new-subscription.coffee index 7851171524..e8294a5885 100644 --- a/services/web/public/coffee/main/new-subscription.coffee +++ b/services/web/public/coffee/main/new-subscription.coffee @@ -4,20 +4,21 @@ define [ "libs/recurly-4.8.5" ], (App)-> - App.controller "NewSubscriptionController", ($scope, MultiCurrencyPricing, abTestManager, $http, sixpack, event_tracking, ccUtils)-> + App.controller "NewSubscriptionController", ($scope, MultiCurrencyPricing, abTestManager, $http, sixpack, event_tracking, ccUtils, ipCookie)-> throw new Error("Recurly API Library Missing.") if typeof recurly is "undefined" $scope.currencyCode = MultiCurrencyPricing.currencyCode $scope.plans = MultiCurrencyPricing.plans $scope.planCode = window.plan_code + $scope.plansVariant = ipCookie('plansVariant') $scope.switchToStudent = ()-> currentPlanCode = window.plan_code planCode = currentPlanCode.replace('collaborator', 'student') - event_tracking.sendMB 'subscription-form-switch-to-student', { plan: window.plan_code } + event_tracking.sendMB 'subscription-form-switch-to-student', { plan: window.plan_code, variant: $scope.plansVariant } window.location = "/user/subscription/new?planCode=#{planCode}¤cy=#{$scope.currencyCode}&cc=#{$scope.data.coupon}" - event_tracking.sendMB "subscription-form", { plan : window.plan_code } + event_tracking.sendMB "subscription-form", { plan : window.plan_code, variant: $scope.plansVariant } $scope.paymentMethod = value: "credit_card" @@ -143,13 +144,14 @@ define [ currencyCode : postData.subscriptionDetails.currencyCode, plan_code : postData.subscriptionDetails.plan_code, coupon_code : postData.subscriptionDetails.coupon_code, - isPaypal : postData.subscriptionDetails.isPaypal + isPaypal : postData.subscriptionDetails.isPaypal, + variant : $scope.plansVariant } $http.post("/user/subscription/create", postData) .then ()-> - event_tracking.sendMB "subscription-submission-success" + event_tracking.sendMB "subscription-submission-success", { variant: $scope.plansVariant } window.location.href = "/user/subscription/thank-you" .catch ()-> $scope.processing = false @@ -234,7 +236,4 @@ define [ {code:'VU',name:'Vanuatu'},{code:'VA',name:'Vatican City'},{code:'VE',name:'Venezuela'},{code:'VN',name:'Vietnam'}, {code:'WK',name:'Wake Island'},{code:'WF',name:'Wallis and Futuna'},{code:'EH',name:'Western Sahara'},{code:'YE',name:'Yemen'}, {code:'ZM',name:'Zambia'},{code:'AX',name:'Åland Islandscode:'} - ] - - sixpack.participate 'plans', ['default', 'more-details'], (chosenVariation, rawResponse)-> - $scope.plansVariant = chosenVariation \ No newline at end of file + ] \ No newline at end of file diff --git a/services/web/public/coffee/main/plans.coffee b/services/web/public/coffee/main/plans.coffee index 1637d79374..f13dff689f 100644 --- a/services/web/public/coffee/main/plans.coffee +++ b/services/web/public/coffee/main/plans.coffee @@ -145,15 +145,21 @@ define [ } - App.controller "PlansController", ($scope, $modal, event_tracking, abTestManager, MultiCurrencyPricing, $http, sixpack, $filter) -> + App.controller "PlansController", ($scope, $modal, event_tracking, abTestManager, MultiCurrencyPricing, $http, sixpack, $filter, ipCookie) -> $scope.showPlans = false $scope.shouldABTestPlans = window.shouldABTestPlans if $scope.shouldABTestPlans sixpack.participate 'plans-details', ['default', 'more-details'], (chosenVariation, rawResponse)-> - $scope.plansVariant = chosenVariation - event_tracking.send 'subscription-funnel', 'plans-page-loaded', chosenVariation + if rawResponse?.status != 'failed' + $scope.plansVariant = chosenVariation + expiration = new Date(); + expiration.setDate(expiration.getDate() + 5); + ipCookie('plansVariant', chosenVariation, {expires: expiration}) + event_tracking.send 'subscription-funnel', 'plans-page-loaded', chosenVariation + else + $scope.timeout = true $scope.showPlans = true @@ -184,9 +190,9 @@ define [ if $scope.ui.view == "annual" plan = "#{plan}_annual" plan = eventLabel(plan, location) - event_tracking.sendMB 'plans-page-start-trial', {plan} + event_tracking.sendMB 'plans-page-start-trial', {plan, variant: $scope.plansVariant} event_tracking.send 'subscription-funnel', 'sign_up_now_button', plan - if $scope.shouldABTestPlans + if $scope.plansVariant sixpack.convert 'plans-details' $scope.switchToMonthly = (e, location) -> diff --git a/services/web/public/img/review-icon-sprite-ol.png b/services/web/public/img/review-icon-sprite-ol.png new file mode 100644 index 0000000000..1e5c7192e4 Binary files /dev/null and b/services/web/public/img/review-icon-sprite-ol.png differ diff --git a/services/web/public/img/review-icon-sprite-ol@2x.png b/services/web/public/img/review-icon-sprite-ol@2x.png new file mode 100644 index 0000000000..87aec7c163 Binary files /dev/null and b/services/web/public/img/review-icon-sprite-ol@2x.png differ diff --git a/services/web/public/stylesheets/app/editor/review-panel.less b/services/web/public/stylesheets/app/editor/review-panel.less index 800a25618f..a7e7d3fdbd 100644 --- a/services/web/public/stylesheets/app/editor/review-panel.less +++ b/services/web/public/stylesheets/app/editor/review-panel.less @@ -922,7 +922,7 @@ } } -.review-icon { +.review-icon when (@is-overleaf = false) { display: inline-block; background: url('/img/review-icon-sprite.png') top/30px no-repeat; width: 30px; @@ -945,10 +945,17 @@ } } -.review-icon when (@is-overleaf) { - background-position-y: -60px; - .toolbar .btn-full-height:hover & { - background-position-y: -60px; +.review-icon when (@is-overleaf = true) { + display: inline-block; + background: url('/img/review-icon-sprite-ol.png') top/30px no-repeat; + width: 30px; + + &::before { + content: '\00a0'; // Non-breakable space. A non-breakable character here makes this icon work like font-awesome. + } + + @media (min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { + background-image: url('/img/review-icon-sprite-ol@2x.png'); } } diff --git a/services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee b/services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee index ae8fedc9c8..24af9971d2 100644 --- a/services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee +++ b/services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee @@ -214,7 +214,7 @@ describe "AuthenticationController", -> beforeEach -> @AuthenticationController._recordFailedLogin = sinon.stub() @AuthenticationController._recordSuccessfulLogin = sinon.stub() - @Modules.hooks.fire = sinon.stub().callsArgWith(2, null, []) + @Modules.hooks.fire = sinon.stub().callsArgWith(3, null, []) # @AuthenticationController.establishUserSession = sinon.stub().callsArg(2) @req.body = email: @email @@ -225,7 +225,7 @@ describe "AuthenticationController", -> describe "when the preDoPassportLogin hooks produce an info object", -> beforeEach -> - @Modules.hooks.fire = sinon.stub().callsArgWith(2, null, [null, {redir: '/somewhere'}, null]) + @Modules.hooks.fire = sinon.stub().callsArgWith(3, null, [null, {redir: '/somewhere'}, null]) it "should stop early and call done with this info object", (done) -> @AuthenticationController.doPassportLogin(@req, @req.body.email, @req.body.password, @cb) diff --git a/services/web/test/unit/coffee/User/UserCreatorTests.coffee b/services/web/test/unit/coffee/User/UserCreatorTests.coffee index f9d88e4cf8..d9118e450f 100644 --- a/services/web/test/unit/coffee/User/UserCreatorTests.coffee +++ b/services/web/test/unit/coffee/User/UserCreatorTests.coffee @@ -20,7 +20,7 @@ describe "UserCreator", -> @addAffiliation = sinon.stub().yields() @UserCreator = SandboxedModule.require modulePath, requires: "../../models/User": User:@UserModel - "logger-sharelatex":{log:->} + "logger-sharelatex":{ log: sinon.stub(), err: sinon.stub() } 'metrics-sharelatex': {timeAsyncMethod: ()->} "../Institutions/InstitutionsAPI": addAffiliation: @addAffiliation @@ -88,3 +88,11 @@ describe "UserCreator", -> process.nextTick () => sinon.assert.calledWith(@addAffiliation, user._id, user.email) done() + + it "should not add affiliation if skipping", (done)-> + attributes = email: @email + options = skip_affiliation: true + @UserCreator.createNewUser attributes, options, (err, user) => + process.nextTick () => + sinon.assert.notCalled(@addAffiliation) + done()