about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md14
-rw-r--r--CONTRIBUTING.md4
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock18
-rw-r--r--app/controllers/settings/preferences_controller.rb1
-rw-r--r--app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.js2
-rw-r--r--app/javascript/flavours/glitch/features/video/index.js10
-rw-r--r--app/javascript/flavours/glitch/styles/about.scss12
-rw-r--r--app/javascript/flavours/glitch/styles/reset.scss2
-rw-r--r--app/javascript/mastodon/features/notifications/components/notification.js30
-rw-r--r--app/javascript/mastodon/features/notifications/containers/notification_container.js57
-rw-r--r--app/javascript/mastodon/features/video/index.js10
-rw-r--r--app/javascript/styles/mastodon/about.scss12
-rw-r--r--app/javascript/styles/mastodon/reset.scss2
-rw-r--r--app/lib/user_settings_decorator.rb5
-rw-r--r--app/models/account.rb1
-rw-r--r--app/models/concerns/omniauthable.rb1
-rw-r--r--app/models/user.rb8
-rw-r--r--app/serializers/rest/status_serializer.rb6
-rw-r--r--app/services/post_status_service.rb7
-rw-r--r--app/services/verify_link_service.rb2
-rw-r--r--app/views/settings/preferences/show.html.haml3
-rw-r--r--app/views/stream_entries/_detailed_status.html.haml2
-rw-r--r--config/locales/simple_form.en.yml2
-rw-r--r--config/settings.yml1
-rw-r--r--db/migrate/20181116173541_copy_account_stats.rb2
-rw-r--r--lib/mastodon/migration_helpers.rb2
-rw-r--r--lib/mastodon/version.rb2
-rw-r--r--spec/services/post_status_service_spec.rb29
29 files changed, 215 insertions, 34 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3bafbe1e9..cfb6b15f5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,20 @@ Changelog
 
 All notable changes to this project will be documented in this file.
 
+## [2.7.1] - 2019-01-28
+### Fixed
+
+- Fix SSO authentication not working due to missing agreement boolean ([Gargron](https://github.com/tootsuite/mastodon/pull/9915))
+- Fix slow fallback of CopyAccountStats migration setting stats to 0 ([Gargron](https://github.com/tootsuite/mastodon/pull/9930))
+- Fix wrong command in migration error message ([angristan](https://github.com/tootsuite/mastodon/pull/9877))
+- Fix initial value of volume slider in video player and handle volume changes ([ThibG](https://github.com/tootsuite/mastodon/pull/9929))
+- Fix missing hotkeys for notifications ([ThibG](https://github.com/tootsuite/mastodon/pull/9927))
+- Fix being able to attach unattached media created by other users ([ThibG](https://github.com/tootsuite/mastodon/pull/9921))
+- Fix unrescued SSL error during link verification ([renatolond](https://github.com/tootsuite/mastodon/pull/9914))
+- Fix Firefox scrollbar color regression ([trwnh](https://github.com/tootsuite/mastodon/pull/9908))
+- Fix scheduled status with media immediately creating a status ([ThibG](https://github.com/tootsuite/mastodon/pull/9894))
+- Fix missing strong style for landing page description ([Kjwon15](https://github.com/tootsuite/mastodon/pull/9892))
+
 ## [2.7.0] - 2019-01-20
 ### Added
 
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8044d04f7..7eceaf142 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -35,7 +35,7 @@ CONTRIBUTING
 =======
 Contributing
 
-Thank you for considering contributing to Mastodon 🐘 
+Thank you for considering contributing to Mastodon 🐘
 
 You can contribute in the following ways:
 
@@ -44,6 +44,8 @@ You can contribute in the following ways:
 - Contributing code to Mastodon by fixing bugs or implementing features
 - Improving the documentation
 
+If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon).
+
 ## Bug reports
 
 Bug reports and feature suggestions can be submitted to [GitHub Issues](https://github.com/tootsuite/mastodon/issues). Please make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected in the past using the search function. Please also use descriptive, concise titles.
diff --git a/Gemfile b/Gemfile
index bf3c2b51d..51595a758 100644
--- a/Gemfile
+++ b/Gemfile
@@ -23,7 +23,7 @@ gem 'paperclip-av-transcoder', '~> 0.6'
 gem 'streamio-ffmpeg', '~> 3.0'
 
 gem 'active_model_serializers', '~> 0.10'
-gem 'addressable', '~> 2.5'
+gem 'addressable', '~> 2.6'
 gem 'bootsnap', '~> 1.3', require: false
 gem 'browser'
 gem 'charlock_holmes', '~> 0.7.6'
diff --git a/Gemfile.lock b/Gemfile.lock
index 0d37a8d36..ce961afa8 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -62,7 +62,7 @@ GEM
       i18n (>= 0.7, < 2)
       minitest (~> 5.1)
       tzinfo (~> 1.1)
-    addressable (2.5.2)
+    addressable (2.6.0)
       public_suffix (>= 2.0.2, < 4.0)
     airbrussh (1.3.0)
       sshkit (>= 1.6.1, != 1.7.0)
@@ -292,8 +292,8 @@ GEM
     json-ld (3.0.2)
       multi_json (~> 1.12)
       rdf (>= 2.2.8, < 4.0)
-    json-ld-preloaded (3.0.0)
-      json-ld (>= 2.2, < 4.0)
+    json-ld-preloaded (3.0.2)
+      json-ld (~> 3.0)
       multi_json (~> 1.12)
       rdf (~> 3.0)
     jsonapi-renderer (0.2.0)
@@ -365,7 +365,7 @@ GEM
       concurrent-ruby (~> 1.0, >= 1.0.2)
       sidekiq (>= 3.5)
       statsd-ruby (~> 1.4, >= 1.4.0)
-    oj (3.7.7)
+    oj (3.7.8)
     omniauth (1.9.0)
       hashie (>= 3.4.6, < 3.7.0)
       rack (>= 1.6.2, < 3)
@@ -391,7 +391,7 @@ GEM
     paperclip-av-transcoder (0.6.4)
       av (~> 0.9.0)
       paperclip (>= 2.5.2)
-    parallel (1.12.1)
+    parallel (1.13.0)
     parallel_tests (2.27.1)
       parallel
     parser (2.6.0.0)
@@ -422,7 +422,7 @@ GEM
       pry (>= 0.10.4)
     public_suffix (3.0.3)
     puma (3.12.0)
-    pundit (2.0.0)
+    pundit (2.0.1)
       activesupport (>= 3.0.0)
     raabro (1.1.6)
     rack (2.0.6)
@@ -515,7 +515,7 @@ GEM
     rspec-mocks (3.8.0)
       diff-lcs (>= 1.2.0, < 2.0)
       rspec-support (~> 3.8.0)
-    rspec-rails (3.8.1)
+    rspec-rails (3.8.2)
       actionpack (>= 3.0)
       activesupport (>= 3.0)
       railties (>= 3.0)
@@ -527,7 +527,7 @@ GEM
       rspec-core (~> 3.0, >= 3.0.0)
       sidekiq (>= 2.4.0)
     rspec-support (3.8.0)
-    rubocop (0.63.0)
+    rubocop (0.63.1)
       jaro_winkler (~> 1.5.1)
       parallel (~> 1.10)
       parser (>= 2.5, != 2.5.1.1)
@@ -657,7 +657,7 @@ PLATFORMS
 DEPENDENCIES
   active_model_serializers (~> 0.10)
   active_record_query_trace (~> 1.5)
-  addressable (~> 2.5)
+  addressable (~> 2.6)
   annotate (~> 2.7)
   aws-sdk-s3 (~> 1.30)
   better_errors (~> 2.5)
diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb
index d4932afd6..241053261 100644
--- a/app/controllers/settings/preferences_controller.rb
+++ b/app/controllers/settings/preferences_controller.rb
@@ -45,6 +45,7 @@ class Settings::PreferencesController < Settings::BaseController
       :setting_hide_network,
       :setting_hide_followers_count,
       :setting_aggregate_reblogs,
+      :setting_show_application,
       notification_emails: %i(follow follow_request reblog favourite mention digest report),
       interactions: %i(must_be_follower must_be_following)
     )
diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.js b/app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.js
index a992b27bb..5adb44f2c 100644
--- a/app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.js
+++ b/app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.js
@@ -26,7 +26,7 @@ export default class ColumnSettings extends React.PureComponent {
         <span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
 
         <div className='column-settings__row'>
-          <SettingText settings={settings} settingKey={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} />
+          <SettingText settings={settings} settingPath={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} />
         </div>
       </div>
     );
diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js
index 30592707c..d8e8791a7 100644
--- a/app/javascript/flavours/glitch/features/video/index.js
+++ b/app/javascript/flavours/glitch/features/video/index.js
@@ -139,6 +139,9 @@ export default class Video extends React.PureComponent {
 
   setVideoRef = c => {
     this.video = c;
+    if (this.video) {
+      this.setState({ volume: this.video.volume, muted: this.video.muted });
+    }
   }
 
   setSeekRef = c => {
@@ -319,6 +322,10 @@ export default class Video extends React.PureComponent {
     }
   }
 
+  handleVolumeChange = () => {
+    this.setState({ volume: this.video.volume, muted: this.video.muted });
+  }
+
   handleOpenVideo = () => {
     const { src, preview, width, height, alt } = this.props;
     const media = fromJS({
@@ -407,6 +414,7 @@ export default class Video extends React.PureComponent {
           onTimeUpdate={this.handleTimeUpdate}
           onLoadedData={this.handleLoadedData}
           onProgress={this.handleProgress}
+          onVolumeChange={this.handleVolumeChange}
         />
 
         <button type='button' className={classNames('video-player__spoiler', { active: !revealed })} onClick={this.toggleReveal}>
@@ -429,7 +437,7 @@ export default class Video extends React.PureComponent {
           <div className='video-player__buttons-bar'>
             <div className='video-player__buttons left'>
               <button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button>
-              <button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onMouseEnter={this.volumeSlider} onMouseLeave={this.volumeSlider} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
+              <button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
               <div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
                 <div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} />
                 <span
diff --git a/app/javascript/flavours/glitch/styles/about.scss b/app/javascript/flavours/glitch/styles/about.scss
index e8f46766a..302de020b 100644
--- a/app/javascript/flavours/glitch/styles/about.scss
+++ b/app/javascript/flavours/glitch/styles/about.scss
@@ -847,6 +847,18 @@ $small-breakpoint: 960px;
       margin-bottom: 0;
     }
 
+    strong {
+      display: inline;
+      margin: 0;
+      padding: 0;
+      font-weight: 700;
+      background: transparent;
+      font-family: inherit;
+      font-size: inherit;
+      line-height: inherit;
+      color: lighten($darker-text-color, 10%);
+    }
+
     .account {
       border-bottom: 0;
       padding: 0;
diff --git a/app/javascript/flavours/glitch/styles/reset.scss b/app/javascript/flavours/glitch/styles/reset.scss
index e24ba8c1c..f54ed5bc7 100644
--- a/app/javascript/flavours/glitch/styles/reset.scss
+++ b/app/javascript/flavours/glitch/styles/reset.scss
@@ -54,7 +54,7 @@ table {
 }
 
 html {
-  scrollbar-color: lighten($ui-base-color, 4%) transparent;
+  scrollbar-color: lighten($ui-base-color, 4%) rgba($base-overlay-background, 0.1);
 }
 
 ::-webkit-scrollbar {
diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js
index 44da423ad..97efff69c 100644
--- a/app/javascript/mastodon/features/notifications/components/notification.js
+++ b/app/javascript/mastodon/features/notifications/components/notification.js
@@ -29,6 +29,10 @@ class Notification extends ImmutablePureComponent {
     onMoveUp: PropTypes.func.isRequired,
     onMoveDown: PropTypes.func.isRequired,
     onMention: PropTypes.func.isRequired,
+    onFavourite: PropTypes.func.isRequired,
+    onReblog: PropTypes.func.isRequired,
+    onToggleHidden: PropTypes.func.isRequired,
+    status: PropTypes.option,
     intl: PropTypes.object.isRequired,
   };
 
@@ -64,14 +68,32 @@ class Notification extends ImmutablePureComponent {
     onMention(notification.get('account'), this.context.router.history);
   }
 
+  handleHotkeyFavourite = () => {
+    const { status } = this.props;
+    if (status) this.props.onFavourite(status);
+  }
+
+  handleHotkeyBoost = e => {
+    const { status } = this.props;
+    if (status) this.props.onReblog(status, e);
+  }
+
+  handleHotkeyToggleHidden = () => {
+    const { status } = this.props;
+    if (status) this.props.onToggleHidden(status);
+  }
+
   getHandlers () {
     return {
-      moveUp: this.handleMoveUp,
-      moveDown: this.handleMoveDown,
+      reply: this.handleMention,
+      favourite: this.handleHotkeyFavourite,
+      boost: this.handleHotkeyBoost,
+      mention: this.handleMention,
       open: this.handleOpen,
       openProfile: this.handleOpenProfile,
-      mention: this.handleMention,
-      reply: this.handleMention,
+      moveUp: this.handleMoveUp,
+      moveDown: this.handleMoveDown,
+      toggleHidden: this.handleHotkeyToggleHidden,
     };
   }
 
diff --git a/app/javascript/mastodon/features/notifications/containers/notification_container.js b/app/javascript/mastodon/features/notifications/containers/notification_container.js
index 921aa460f..78576c760 100644
--- a/app/javascript/mastodon/features/notifications/containers/notification_container.js
+++ b/app/javascript/mastodon/features/notifications/containers/notification_container.js
@@ -1,14 +1,31 @@
 import { connect } from 'react-redux';
-import { makeGetNotification } from '../../../selectors';
+import { makeGetNotification, makeGetStatus } from '../../../selectors';
 import Notification from '../components/notification';
+import { openModal } from '../../../actions/modal';
 import { mentionCompose } from '../../../actions/compose';
+import {
+  reblog,
+  favourite,
+  unreblog,
+  unfavourite,
+} from '../../../actions/interactions';
+import {
+  hideStatus,
+  revealStatus,
+} from '../../../actions/statuses';
+import { boostModal } from '../../../initial_state';
 
 const makeMapStateToProps = () => {
   const getNotification = makeGetNotification();
+  const getStatus = makeGetStatus();
 
-  const mapStateToProps = (state, props) => ({
-    notification: getNotification(state, props.notification, props.accountId),
-  });
+  const mapStateToProps = (state, props) => {
+    const notification = getNotification(state, props.notification, props.accountId);
+    return {
+      notification: notification,
+      status: notification.get('status') ? getStatus(state, { id: notification.get('status') }) : null,
+    };
+  };
 
   return mapStateToProps;
 };
@@ -17,6 +34,38 @@ const mapDispatchToProps = dispatch => ({
   onMention: (account, router) => {
     dispatch(mentionCompose(account, router));
   },
+
+  onModalReblog (status) {
+    dispatch(reblog(status));
+  },
+
+  onReblog (status, e) {
+    if (status.get('reblogged')) {
+      dispatch(unreblog(status));
+    } else {
+      if (e.shiftKey || !boostModal) {
+        this.onModalReblog(status);
+      } else {
+        dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
+      }
+    }
+  },
+
+  onFavourite (status) {
+    if (status.get('favourited')) {
+      dispatch(unfavourite(status));
+    } else {
+      dispatch(favourite(status));
+    }
+  },
+
+  onToggleHidden (status) {
+    if (status.get('hidden')) {
+      dispatch(revealStatus(status.get('id')));
+    } else {
+      dispatch(hideStatus(status.get('id')));
+    }
+  },
 });
 
 export default connect(makeMapStateToProps, mapDispatchToProps)(Notification);
diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js
index 3650fddb6..0d0c24d71 100644
--- a/app/javascript/mastodon/features/video/index.js
+++ b/app/javascript/mastodon/features/video/index.js
@@ -136,6 +136,9 @@ class Video extends React.PureComponent {
 
   setVideoRef = c => {
     this.video = c;
+    if (this.video) {
+      this.setState({ volume: this.video.volume, muted: this.video.muted });
+    }
   }
 
   setSeekRef = c => {
@@ -302,6 +305,10 @@ class Video extends React.PureComponent {
     }
   }
 
+  handleVolumeChange = () => {
+    this.setState({ volume: this.video.volume, muted: this.video.muted });
+  }
+
   handleOpenVideo = () => {
     const { src, preview, width, height, alt } = this.props;
     const media = fromJS({
@@ -387,6 +394,7 @@ class Video extends React.PureComponent {
           onTimeUpdate={this.handleTimeUpdate}
           onLoadedData={this.handleLoadedData}
           onProgress={this.handleProgress}
+          onVolumeChange={this.handleVolumeChange}
         />
 
         <button type='button' className={classNames('video-player__spoiler', { active: !revealed })} onClick={this.toggleReveal}>
@@ -409,7 +417,7 @@ class Video extends React.PureComponent {
           <div className='video-player__buttons-bar'>
             <div className='video-player__buttons left'>
               <button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button>
-              <button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onMouseEnter={this.volumeSlider} onMouseLeave={this.volumeSlider} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
+              <button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
               <div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
                 <div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} />
                 <span
diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss
index c6f249fab..b6c92a09e 100644
--- a/app/javascript/styles/mastodon/about.scss
+++ b/app/javascript/styles/mastodon/about.scss
@@ -845,6 +845,18 @@ $small-breakpoint: 960px;
       margin-bottom: 0;
     }
 
+    strong {
+      display: inline;
+      margin: 0;
+      padding: 0;
+      font-weight: 700;
+      background: transparent;
+      font-family: inherit;
+      font-size: inherit;
+      line-height: inherit;
+      color: lighten($darker-text-color, 10%);
+    }
+
     .account {
       border-bottom: 0;
       padding: 0;
diff --git a/app/javascript/styles/mastodon/reset.scss b/app/javascript/styles/mastodon/reset.scss
index e24ba8c1c..f54ed5bc7 100644
--- a/app/javascript/styles/mastodon/reset.scss
+++ b/app/javascript/styles/mastodon/reset.scss
@@ -54,7 +54,7 @@ table {
 }
 
 html {
-  scrollbar-color: lighten($ui-base-color, 4%) transparent;
+  scrollbar-color: lighten($ui-base-color, 4%) rgba($base-overlay-background, 0.1);
 }
 
 ::-webkit-scrollbar {
diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb
index 569255f6e..367ba9a83 100644
--- a/app/lib/user_settings_decorator.rb
+++ b/app/lib/user_settings_decorator.rb
@@ -35,6 +35,7 @@ class UserSettingsDecorator
     user.settings['skin']                = skin_preference if change?('setting_skin')
     user.settings['hide_network']        = hide_network_preference if change?('setting_hide_network')
     user.settings['aggregate_reblogs']   = aggregate_reblogs_preference if change?('setting_aggregate_reblogs')
+    user.settings['show_application']    = show_application_preference if change?('setting_show_application')
   end
 
   def merged_notification_emails
@@ -109,6 +110,10 @@ class UserSettingsDecorator
     boolean_cast_setting 'setting_hide_network'
   end
 
+  def show_application_preference
+    boolean_cast_setting 'setting_show_application'
+  end
+
   def default_language_preference
     settings['setting_default_language']
   end
diff --git a/app/models/account.rb b/app/models/account.rb
index 1ee63c738..c86c6fdb5 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -113,6 +113,7 @@ class Account < ApplicationRecord
            :staff?,
            :locale,
            :hides_network?,
+           :shows_application?,
            to: :user,
            prefix: true,
            allow_nil: true
diff --git a/app/models/concerns/omniauthable.rb b/app/models/concerns/omniauthable.rb
index f263fe7af..4dd2e9383 100644
--- a/app/models/concerns/omniauthable.rb
+++ b/app/models/concerns/omniauthable.rb
@@ -63,6 +63,7 @@ module Omniauthable
       {
         email: email || "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
         password: Devise.friendly_token[0, 20],
+        agreement: true,
         account_attributes: {
           username: ensure_unique_username(auth.uid),
           display_name: display_name,
diff --git a/app/models/user.rb b/app/models/user.rb
index 0425c1772..ce9ef2a13 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -100,7 +100,7 @@ class User < ApplicationRecord
 
   delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :favourite_modal, :delete_modal,
            :reduce_motion, :system_font_ui, :noindex, :flavour, :skin, :display_media, :hide_network, :hide_followers_count,
-           :expand_spoilers, :default_language, :aggregate_reblogs, to: :settings, prefix: :setting, allow_nil: false
+           :expand_spoilers, :default_language, :aggregate_reblogs, :show_application, to: :settings, prefix: :setting, allow_nil: false
 
   attr_reader :invite_code
 
@@ -244,6 +244,10 @@ class User < ApplicationRecord
     @aggregates_reblogs ||= settings.aggregate_reblogs
   end
 
+  def shows_application?
+    @shows_application ||= settings.shows_application
+  end
+
   def token_for_app(a)
     return nil if a.nil? || a.owner != self
     Doorkeeper::AccessToken
@@ -295,6 +299,7 @@ class User < ApplicationRecord
 
   def self.pam_get_user(attributes = {})
     return nil unless attributes[:email]
+
     resource =
       if Devise.check_at_sign && !attributes[:email].index('@')
         joins(:account).find_by(accounts: { username: attributes[:email] })
@@ -304,6 +309,7 @@ class User < ApplicationRecord
 
     if resource.blank?
       resource = new(email: attributes[:email], agreement: true)
+
       if Devise.check_at_sign && !resource[:email].index('@')
         resource[:email] = Rpam2.getenv(resource.find_pam_service, attributes[:email], attributes[:password], 'email', false)
         resource[:email] = "#{attributes[:email]}@#{resource.find_pam_suffix}" unless resource[:email]
diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb
index dd8482eea..b72eebb10 100644
--- a/app/serializers/rest/status_serializer.rb
+++ b/app/serializers/rest/status_serializer.rb
@@ -14,7 +14,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
   attribute :local_only if :local?
 
   belongs_to :reblog, serializer: REST::StatusSerializer
-  belongs_to :application
+  belongs_to :application, if: :show_application?
   belongs_to :account, serializer: REST::AccountSerializer
 
   has_many :media_attachments, serializer: REST::MediaAttachmentSerializer
@@ -40,6 +40,10 @@ class REST::StatusSerializer < ActiveModel::Serializer
     !current_user.nil?
   end
 
+  def show_application?
+    object.account.user_shows_application? || (current_user? && current_user.account_id == object.account_id)
+  end
+
   def visibility
     # This visibility is masked behind "private"
     # to avoid API changes because there are no
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index 68cffe915..5d431c42a 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -69,7 +69,10 @@ class PostStatusService < BaseService
   end
 
   def schedule_status!
-    if @account.statuses.build(status_attributes).valid?
+    status_for_validation = @account.statuses.build(status_attributes)
+    if status_for_validation.valid?
+      status_for_validation.destroy
+
       # The following transaction block is needed to wrap the UPDATEs to
       # the media attachments when the scheduled status is created
 
@@ -95,7 +98,7 @@ class PostStatusService < BaseService
 
     raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4
 
-    @media = MediaAttachment.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i))
+    @media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i))
 
     raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:video?)
   end
diff --git a/app/services/verify_link_service.rb b/app/services/verify_link_service.rb
index c65578761..878a2188d 100644
--- a/app/services/verify_link_service.rb
+++ b/app/services/verify_link_service.rb
@@ -10,7 +10,7 @@ class VerifyLinkService < BaseService
     return unless link_back_present?
 
     field.mark_verified!
-  rescue HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
+  rescue OpenSSL::SSL::SSLError, HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
     Rails.logger.debug "Error fetching link #{@url}: #{e}"
     nil
   end
diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml
index 6510e0560..c666bafb5 100644
--- a/app/views/settings/preferences/show.html.haml
+++ b/app/views/settings/preferences/show.html.haml
@@ -34,6 +34,9 @@
   .fields-group
     = f.input :setting_hide_network, as: :boolean, wrapper: :with_label
 
+  .fields-group
+    = f.input :setting_show_application, as: :boolean, wrapper: :with_label
+
   - unless Setting.hide_followers_count
     .fields-group
       = f.input :setting_hide_followers_count, as: :boolean, wrapper: :with_label
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
index 18265e110..e123d657f 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/app/views/stream_entries/_detailed_status.html.haml
@@ -39,7 +39,7 @@
     = link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: stream_link_target, rel: 'noopener' do
       %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
     ·
-    - if status.application
+    - if status.application && @account.user&.setting_show_application
       - if status.application.website.blank?
         %strong.detailed-status__application= status.application.name
       - else
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index c75471658..674abff63 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -33,6 +33,7 @@ en:
         setting_display_media_show_all: Always show media marked as sensitive
         setting_hide_network: Who you follow and who follows you will not be shown on your profile
         setting_noindex: Affects your public profile and status pages
+        setting_show_application: The application you use to toot will be displayed in the detailed view of your toots
         setting_skin: Reskins the selected Mastodon flavour
         username: Your username will be unique on %{domain}
         whole_word: When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word
@@ -102,6 +103,7 @@ en:
         setting_hide_network: Hide your network
         setting_noindex: Opt-out of search engine indexing
         setting_reduce_motion: Reduce motion in animations
+        setting_show_application: Disclose application used to send toots
         setting_skin: Skin
         setting_system_font_ui: Use system's default font
         setting_unfollow_modal: Show confirmation dialog before unfollowing someone
diff --git a/config/settings.yml b/config/settings.yml
index 4f070240a..af596b738 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -27,6 +27,7 @@ defaults: &defaults
   expand_spoilers: false
   preview_sensitive_media: false
   reduce_motion: false
+  show_application: false
   system_font_ui: false
   noindex: false
   hide_followers_count: false
diff --git a/db/migrate/20181116173541_copy_account_stats.rb b/db/migrate/20181116173541_copy_account_stats.rb
index bb523fbbd..8e27eb11b 100644
--- a/db/migrate/20181116173541_copy_account_stats.rb
+++ b/db/migrate/20181116173541_copy_account_stats.rb
@@ -44,7 +44,7 @@ class CopyAccountStats < ActiveRecord::Migration[5.2]
     # uniqueness violations that we need to skip over
     Account.unscoped.select('id, statuses_count, following_count, followers_count, created_at, updated_at').find_each do |account|
       begin
-        params = [[nil, account.id], [nil, account.statuses_count], [nil, account.following_count], [nil, account.followers_count], [nil, account.created_at], [nil, account.updated_at]]
+        params = [[nil, account.id], [nil, account[:statuses_count]], [nil, account[:following_count]], [nil, account[:followers_count]], [nil, account.created_at], [nil, account.updated_at]]
         exec_insert('INSERT INTO account_stats (account_id, statuses_count, following_count, followers_count, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6)', nil, params)
       rescue ActiveRecord::RecordNotUnique
         next
diff --git a/lib/mastodon/migration_helpers.rb b/lib/mastodon/migration_helpers.rb
index f5dc7e1c6..146eba8ec 100644
--- a/lib/mastodon/migration_helpers.rb
+++ b/lib/mastodon/migration_helpers.rb
@@ -889,7 +889,7 @@ table #{table}.
 If you are using PostgreSQL you can solve this by logging in to the GitLab
 database (#{dbname}) using a super user and running:
 
-    ALTER #{user} WITH SUPERUSER
+    ALTER USER #{user} WITH SUPERUSER
 
 For MySQL you instead need to run:
 
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index 328159fd3..fad5b5c47 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -13,7 +13,7 @@ module Mastodon
     end
 
     def patch
-      0
+      1
     end
 
     def pre
diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb
index 3774fed6f..facbe977f 100644
--- a/spec/services/post_status_service_spec.rb
+++ b/spec/services/post_status_service_spec.rb
@@ -36,6 +36,20 @@ RSpec.describe PostStatusService, type: :service do
     expect(status.params['text']).to eq 'Hi future!'
   end
 
+  it 'does not immediately create a status when scheduling a status' do
+    account = Fabricate(:account)
+    media = Fabricate(:media_attachment)
+    future  = Time.now.utc + 2.hours
+
+    status = subject.call(account, text: 'Hi future!', media_ids: [media.id], scheduled_at: future)
+
+    expect(status).to be_a ScheduledStatus
+    expect(status.scheduled_at).to eq future
+    expect(status.params['text']).to eq 'Hi future!'
+    expect(media.reload.status).to be_nil
+    expect(Status.where(text: 'Hi future!').exists?).to be_falsey
+  end
+
   it 'creates response to the original status of boost' do
     boosted_status = Fabricate(:status)
     in_reply_to_status = Fabricate(:status, reblog: boosted_status)
@@ -153,7 +167,7 @@ RSpec.describe PostStatusService, type: :service do
 
   it 'attaches the given media to the created status' do
     account = Fabricate(:account)
-    media = Fabricate(:media_attachment)
+    media = Fabricate(:media_attachment, account: account)
 
     status = subject.call(
       account,
@@ -164,6 +178,19 @@ RSpec.describe PostStatusService, type: :service do
     expect(media.reload.status).to eq status
   end
 
+  it 'does not attach media from another account to the created status' do
+    account = Fabricate(:account)
+    media = Fabricate(:media_attachment, account: Fabricate(:account))
+
+    status = subject.call(
+      account,
+      text: "test status update",
+      media_ids: [media.id],
+    )
+
+    expect(media.reload.status).to eq nil
+  end
+
   it 'does not allow attaching more than 4 files' do
     account = Fabricate(:account)