FEATURE: allows published pages to be public (#10053)

This commit is contained in:
Joffrey JAFFEUX 2020-06-17 12:42:20 +02:00 committed by GitHub
parent 7d289a4f3e
commit 9da3a7f436
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 196 additions and 30 deletions

View File

@ -35,6 +35,7 @@ export default Controller.extend(ModalFunctionality, StateHelpers, {
this.state === States.existing
);
}),
showUnpublish: computed("state", function() {
return this.state === States.existing || this.state === States.unpublishing;
}),
@ -95,7 +96,7 @@ export default Controller.extend(ModalFunctionality, StateHelpers, {
this.set("state", States.saving);
return this.publishedPage
.update({ slug: this.publishedPage.slug })
.update(this.publishedPage.getProperties("slug", "public"))
.then(() => {
this.set("state", States.existing);
this.model.set("publishedPage", this.publishedPage);
@ -110,11 +111,17 @@ export default Controller.extend(ModalFunctionality, StateHelpers, {
startNew() {
this.setProperties({
state: States.new,
publishedPage: this.store.createRecord("published_page", {
id: this.model.id,
slug: this.model.slug
})
publishedPage: this.store.createRecord(
"published_page",
this.model.getProperties("id", "slug", "public")
)
});
this.checkSlug();
},
@action
onChangePublic(isPublic) {
this.publishedPage.set("public", isPublic);
this.publish();
}
});

View File

@ -6,8 +6,29 @@
<p class="publish-description">{{i18n "topic.publish_page.description"}}</p>
<form>
<label>{{i18n "topic.publish_page.slug"}}</label>
{{text-field value=publishedPage.slug onChange=(action "checkSlug") onChangeImmediate=(action "startCheckSlug") disabled=existing class="publish-slug"}}
<div class="controls">
<label>{{i18n "topic.publish_page.slug"}}</label>
{{text-field
value=publishedPage.slug
onChange=(action "checkSlug")
onChangeImmediate=(action "startCheckSlug")
disabled=existing
class="publish-slug"
}}
</div>
<div class="controls">
<label>{{i18n "topic.publish_page.public"}}</label>
<p class="description">
{{input
type="checkbox"
checked=publishedPage.public
click=(action "onChangePublic" value="target.checked")
}}
{{i18n "topic.publish_page.public_description"}}
</p>
</div>
</form>
<div class="publish-url">
@ -40,14 +61,15 @@
<div class="modal-footer">
{{#if showUnpublish}}
{{d-button icon="times" label="close" action=(action "closeModal")}}
{{d-button
label="topic.publish_page.unpublish"
icon="trash-alt"
class="btn-danger"
isLoading=unpublishing
action=(action "unpublish") }}
action=(action "unpublish")
}}
{{d-button class="close-publish-page" icon="times" label="close" action=(action "closeModal")}}
{{else if unpublished}}
{{d-button label="topic.publish_page.publishing_settings" action=(action "startNew")}}
{{else}}

View File

@ -90,6 +90,9 @@
{{#if model.publishedPage}}
<div class="published-page-notice">
<div class="details">
{{#if model.publishedPage.public}}
<span class="is-public">{{i18n "topic.publish_page.public"}}</span>
{{/if}}
{{i18n "topic.publish_page.topic_published"}}
<div>
<a href={{model.publishedPage.url}} target="_blank" rel="noopener noreferrer">{{model.publishedPage.url}}</a>

View File

@ -689,24 +689,45 @@
}
}
.publish-page-modal .modal-body {
p.publish-description {
margin-top: 0;
}
input.publish-slug {
width: 100%;
}
.publish-page-modal {
.modal-body {
p.publish-description {
margin-top: 0;
}
input.publish-slug {
width: 100%;
}
.publish-url {
margin-bottom: 1em;
.example-url,
.invalid-slug {
font-weight: bold;
.publish-url {
margin-bottom: 1em;
.example-url,
.invalid-slug {
font-weight: bold;
}
}
.publish-slug:disabled {
cursor: not-allowed;
}
.controls {
margin-bottom: 1em;
.description {
margin: 0;
display: flex;
align-items: center;
}
}
}
.publish-slug:disabled {
cursor: not-allowed;
.modal-footer {
display: flex;
.close-publish-page {
margin-left: auto;
margin-right: 0;
}
}
}

View File

@ -305,4 +305,13 @@ a.topic-featured-link {
2}
);
align-items: center;
.is-public {
padding: 0.25em 0.5em;
font-size: $font-down-2;
background: $tertiary;
color: $secondary;
border-radius: 3px;
text-transform: lowercase;
}
}

View File

@ -5,6 +5,7 @@ class PublishedPagesController < ApplicationController
skip_before_action :preload_json
skip_before_action :check_xhr, :verify_authenticity_token, only: [:show]
before_action :ensure_publish_enabled
before_action :redirect_to_login_if_required, except: [:show]
def show
params.require(:slug)
@ -12,7 +13,22 @@ class PublishedPagesController < ApplicationController
pp = PublishedPage.find_by(slug: params[:slug])
raise Discourse::NotFound unless pp
guardian.ensure_can_see!(pp.topic)
return if enforce_login_required!
if !pp.public
begin
guardian.ensure_can_see!(pp.topic)
rescue Discourse::InvalidAccess => e
return rescue_discourse_actions(
:invalid_access,
403,
include_ember: false,
custom_message: e.custom_message,
group: e.group
)
end
end
@topic = pp.topic
@canonical_url = @topic.url
@ -37,7 +53,15 @@ class PublishedPagesController < ApplicationController
end
def upsert
result, pp = PublishedPage.publish!(current_user, fetch_topic, params[:published_page][:slug].strip)
pp_params = params.require(:published_page)
result, pp = PublishedPage.publish!(
current_user,
fetch_topic,
pp_params[:slug].strip,
pp_params.permit(:public)
)
json_result(pp, serializer: PublishedPageSerializer) { result }
end
@ -68,4 +92,13 @@ private
raise Discourse::NotFound unless SiteSetting.enable_page_publishing?
end
def enforce_login_required!
if SiteSetting.login_required? &&
!current_user &&
!SiteSetting.show_published_pages_login_required? &&
redirect_to_login
true
end
end
end

View File

@ -23,12 +23,13 @@ class PublishedPage < ActiveRecord::Base
"#{Discourse.base_url}#{path}"
end
def self.publish!(publisher, topic, slug)
def self.publish!(publisher, topic, slug, options = {})
pp = nil
transaction do
pp = find_or_initialize_by(topic: topic)
pp.slug = slug.strip
pp.public = options[:public] || false
if pp.save
StaffActionLogger.new(publisher).log_published_page(topic.id, slug)
@ -56,6 +57,7 @@ end
# slug :string not null
# created_at :datetime not null
# updated_at :datetime not null
# public :boolean default(FALSE), not null
#
# Indexes
#

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
class PublishedPageSerializer < ApplicationSerializer
attributes :id, :slug
attributes :id, :slug, :public
def id
object.topic_id

View File

@ -2513,11 +2513,14 @@ en:
publish: "Publish"
description: "When a topic is published as a page, its URL can be shared and it will displayed with custom styling."
slug: "Slug"
public: "Public"
public_description: "People can see the page even if the associated topic is private."
publish_url: "Your page has been published at:"
topic_published: "Your topic has been published at:"
preview_url: "Your page will be published at:"
invalid_slug: "Sorry, you can't publish this page."
unpublish: "Unpublish"
update: "Update"
unpublished: "Your page has been unpublished and is no longer accessible."
publishing_settings: "Publishing Settings"

View File

@ -2131,6 +2131,7 @@ en:
returning_user_notice_tl: "Minimum trust level required to see returning user post notices."
returning_users_days: "How many days should pass before a user is considered to be returning."
enable_page_publishing: "Allow staff members to publish topics to new URLs with their own styling."
show_published_pages_login_required: "Anonymous users can see published pages, even when login is required."
default_email_digest_frequency: "How often users receive summary emails by default."
default_include_tl0_in_digests: "Include posts from new users in summary emails by default. Users can change this in their preferences."

View File

@ -947,6 +947,8 @@ posting:
max: 36500
enable_page_publishing:
default: false
show_published_pages_login_required:
default: false
email:
email_time_window_mins:

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddPublicFieldToPublishedPages < ActiveRecord::Migration[6.0]
def change
add_column :published_pages, :public, :boolean, null: false, default: false
end
end

View File

@ -2,5 +2,6 @@
Fabricator(:published_page) do
topic
slug "published-page-test"
slug "published-page-test-#{SecureRandom.hex}"
public false
end

View File

@ -59,6 +59,15 @@ RSpec.describe PublishedPagesController do
expect(response.status).to eq(403)
end
context "published page is public" do
fab!(:public_published_page) { Fabricate(:published_page, public: true) }
it "returns 200 for a topic you can't see" do
get public_published_page.path
expect(response.status).to eq(200)
end
end
context "as an admin" do
before do
sign_in(admin)
@ -89,7 +98,42 @@ RSpec.describe PublishedPagesController do
it "defines correct css classes on body" do
get published_page.path
expect(response.body).to include("<body class=\"published-page published-page-test topic-#{published_page.topic_id} recipes uncategorized\">")
expect(response.body).to include("<body class=\"published-page #{published_page.slug} topic-#{published_page.topic_id} recipes uncategorized\">")
end
context "login is required" do
before do
SiteSetting.login_required = true
SiteSetting.show_published_pages_login_required = false
end
context "a user is connected" do
before do
sign_in(user)
end
it "returns 200" do
get published_page.path
expect(response.status).to eq(200)
end
end
context "no user connected" do
it "redirects to login page" do
expect(get(published_page.path)).to redirect_to("/login")
end
context "show public pages with login required is enabled" do
before do
SiteSetting.show_published_pages_login_required = true
end
it "returns 200" do
get published_page.path
expect(response.status).to eq(200)
end
end
end
end
end
end
@ -113,6 +157,7 @@ RSpec.describe PublishedPagesController do
expect(response).to be_successful
expect(response.parsed_body['published_page']).to be_present
expect(response.parsed_body['published_page']['slug']).to eq("i-hate-salt")
expect(response.parsed_body['published_page']['public']).to eq(false)
expect(PublishedPage.exists?(topic_id: response.parsed_body['published_page']['id'])).to eq(true)
expect(UserHistory.exists?(
@ -122,6 +167,16 @@ RSpec.describe PublishedPagesController do
)).to be(true)
end
it "allows to set public field" do
put "/pub/by-topic/#{topic.id}.json", params: { published_page: { slug: 'i-hate-salt', public: true } }
expect(response).to be_successful
expect(response.parsed_body['published_page']).to be_present
expect(response.parsed_body['published_page']['slug']).to eq("i-hate-salt")
expect(response.parsed_body['published_page']['public']).to eq(true)
expect(PublishedPage.exists?(topic_id: response.parsed_body['published_page']['id'])).to eq(true)
end
it "returns an error if the slug is already taken" do
PublishedPage.create!(slug: 'i-hate-salt', topic: Fabricate(:topic))
put "/pub/by-topic/#{topic.id}.json", params: { published_page: { slug: 'i-hate-salt' } }