mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 18:13:38 +08:00
35284c77f1
Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.66.1 to 1.67.0. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/rubocop/rubocop/releases">rubocop's releases</a>.</em></p> <blockquote> <h2>RuboCop 1.67</h2> <h3>New features</h3> <ul> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13259">#13259</a>: Add new <code>Lint/DuplicateSetElement</code> cop. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13223">#13223</a>: Add <code>AllowRBSInlineAnnotation</code> config option to <code>Layout/LeadingCommentSpace</code> to support RBS::Inline style annotation comments. ([<a href="https://github.com/tk0miya"><code>@tk0miya</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13310">#13310</a>: Display analysis Ruby version in <code>rubocop -V</code>. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> </ul> <h3>Bug fixes</h3> <ul> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13314">#13314</a>: Fix a false negative for <code>Style/Semicolon</code> when using a semicolon between a closing parenthesis after a line break and a consequent expression. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13217">#13217</a>: Fix a false positive in <code>Lint/ParenthesesAsGroupedExpression</code> with compound ranges. ([<a href="https://github.com/gsamokovarov"><code>@gsamokovarov</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13268">#13268</a>: Fix a false positive for <code>Style/BlockDelimiters</code> when a single line do-end block with an inline <code>rescue</code> with a semicolon before <code>rescue</code>. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13298">#13298</a>: Fix an error for <code>Layout/AccessModifierIndentation</code> when the access modifier is on the same line as the class definition. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13198">#13198</a>: Fix an error for <code>Style/OneLineConditional</code> when using nested if/then/else/end. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13316">#13316</a>: Fix an incorrect autocorrect for <code>Lint/ImplicitStringConcatenation</code> with <code>Lint/TripleQuotes</code> when string literals with triple quotes are used. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13220">#13220</a>: Fix an incorrect autocorrect for <code>Style/ArgumentsForwarding</code> when using only forwarded arguments in brackets. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13202">#13202</a>: Fix an incorrect autocorrect for <code>Style/CombinableLoops</code> when looping over the same data with different block variable names. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13291">#13291</a>: Fix an incorrect autocorrect for <code>Style/RescueModifier</code> when using modifier rescue for method call with heredoc argument. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13226">#13226</a>: Fix <code>--auto-gen-config</code> when passing an absolute config path. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13225">#13225</a>: Avoid syntax error when correcting <code>Style/OperatorMethodCall</code> with <code>/</code> operations followed by a parenthesized argument. ([<a href="https://github.com/dvandersluis"><code>@dvandersluis</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13235">#13235</a>: Fix an error for <code>Style/IfUnlessModifier</code> when multiline <code>if</code> that fits on one line and using implicit method call with hash value omission syntax. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13219">#13219</a>: Fix a false positive for <code>Style/ArgumentsForwarding</code> with Ruby 3.0 and optional position arguments. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13271">#13271</a>: Fix a false positive for <code>Lint/AmbiguousRange</code> when using rational literals. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13260">#13260</a>: Fix a false positive for <code>Lint/RedundantSafeNavigation</code> with namespaced constants. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13224">#13224</a>: Fix false positives for <code>Style/OperatorMethodCall</code> with named forwarding. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13213">#13213</a>: Fix false positives for <code>Style/AccessModifierDeclarations</code> when <code>AllowModifiersOnAttrs: true</code> and using splat with a percent symbol array, or with a constant. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13145">#13145</a>: Fix false positives for <code>Style/RedundantLineContinuation</code> when line continuations with comparison operator and the LHS is wrapped in parentheses. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/12875">#12875</a>: Fix false positive for <code>Style/ArgumentsForwarding</code> when argument is used inside a block. ([<a href="https://github.com/dvandersluis"><code>@dvandersluis</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13239">#13239</a>: Fix false positive for <code>Style/CollectionCompact</code> when using <code>delete_if</code>. ([<a href="https://github.com/masato-bkn"><code>@masato-bkn</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13210">#13210</a>: Fix omit_parentheses style for pattern match with value omission in single-line branch. ([<a href="https://github.com/gsamokovarov"><code>@gsamokovarov</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13149">#13149</a>: Handle crashes in custom Ruby extractors more gracefully. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13319">#13319</a>: Handle literal forward slashes inside a <code>regexp</code> in <code>Lint/LiteralInInterpolation</code>. ([<a href="https://github.com/dvandersluis"><code>@dvandersluis</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13208">#13208</a>: Fix an incorrect autocorrect for <code>Style/IfWithSemicolon</code> when single-line <code>if/;/end</code> when the then body contains a method call with <code>[]</code> or <code>[]=</code>. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13318">#13318</a>: Prevent modifying blocks with <code>Style/HashEachMethods</code> if the hash is modified within the block. ([<a href="https://github.com/dvandersluis"><code>@dvandersluis</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13293">#13293</a>: Fix <code>TargetRubyVersion</code> from a gemspec when the gemspec is not named like the folder it is located in. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13211">#13211</a>: Fix wrong autocorrect for <code>Style/GuardClause</code> when using heredoc without <code>else</code> branch. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13215">#13215</a>: Fix wrong autocorrect for <code>Lint/BigDecimalNew</code> when using <code>::BigDecimal.new</code>. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13215">#13215</a>: Fix wrong autocorrect for <code>Style/MethodCallWithArgsParentheses</code> with <code>EnforcedStyle: omit_parentheses</code> and whitespace. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13302">#13302</a>: Fix incompatible autocorrect between <code>Style/RedundantBegin</code> and <code>Style/BlockDelimiters</code> with <code>EnforcedStyle: braces_for_chaining</code>. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> </ul> <h3>Changes</h3> <ul> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13221">#13221</a>: Do not group accessors having RBS::Inline annotation comments in <code>Style/AccessorGrouping</code>. ([<a href="https://github.com/tk0miya"><code>@tk0miya</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13286">#13286</a>: Add <code>AllowedMethods</code> configuration to <code>Layout/FirstMethodArgumentLineBreak</code>. ([<a href="https://github.com/dvandersluis"><code>@dvandersluis</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13110">#13110</a>: Add support in <code>Style/ArgumentsForwarding</code> for detecting forwarding of all anonymous arguments. ([<a href="https://github.com/dvandersluis"><code>@dvandersluis</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13222">#13222</a>: Allow to write RBS::Inline annotation comments after method definition in <code>Style/CommentedKeyword</code>. ([<a href="https://github.com/tk0miya"><code>@tk0miya</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13253">#13253</a>: Emit a deprecation when custom cops inherit from <code>RuboCop::Cop::Cop</code>. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13300">#13300</a>: Set <code>EnforcedShorthandSyntax: either</code> by default for <code>Style/HashSyntax</code>. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13254">#13254</a>: Enhance the autocorrect for <code>Naming/InclusiveLanguage</code> when a sole suggestion is set. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13232">#13232</a>: Make server mode aware of auto-restart for local config update. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md">rubocop's changelog</a>.</em></p> <blockquote> <h2>1.67.0 (2024-10-15)</h2> <h3>New features</h3> <ul> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13259">#13259</a>: Add new <code>Lint/DuplicateSetElement</code> cop. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13223">#13223</a>: Add <code>AllowRBSInlineAnnotation</code> config option to <code>Layout/LeadingCommentSpace</code> to support RBS::Inline style annotation comments. ([<a href="https://github.com/tk0miya"><code>@tk0miya</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13310">#13310</a>: Display analysis Ruby version in <code>rubocop -V</code>. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> </ul> <h3>Bug fixes</h3> <ul> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13314">#13314</a>: Fix a false negative for <code>Style/Semicolon</code> when using a semicolon between a closing parenthesis after a line break and a consequent expression. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13217">#13217</a>: Fix a false positive in <code>Lint/ParenthesesAsGroupedExpression</code> with compound ranges. ([<a href="https://github.com/gsamokovarov"><code>@gsamokovarov</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13268">#13268</a>: Fix a false positive for <code>Style/BlockDelimiters</code> when a single line do-end block with an inline <code>rescue</code> with a semicolon before <code>rescue</code>. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13298">#13298</a>: Fix an error for <code>Layout/AccessModifierIndentation</code> when the access modifier is on the same line as the class definition. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13198">#13198</a>: Fix an error for <code>Style/OneLineConditional</code> when using nested if/then/else/end. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13316">#13316</a>: Fix an incorrect autocorrect for <code>Lint/ImplicitStringConcatenation</code> with <code>Lint/TripleQuotes</code> when string literals with triple quotes are used. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13220">#13220</a>: Fix an incorrect autocorrect for <code>Style/ArgumentsForwarding</code> when using only forwarded arguments in brackets. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13202">#13202</a>: Fix an incorrect autocorrect for <code>Style/CombinableLoops</code> when looping over the same data with different block variable names. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13291">#13291</a>: Fix an incorrect autocorrect for <code>Style/RescueModifier</code> when using modifier rescue for method call with heredoc argument. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13226">#13226</a>: Fix <code>--auto-gen-config</code> when passing an absolute config path. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13225">#13225</a>: Avoid syntax error when correcting <code>Style/OperatorMethodCall</code> with <code>/</code> operations followed by a parenthesized argument. ([<a href="https://github.com/dvandersluis"><code>@dvandersluis</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13235">#13235</a>: Fix an error for <code>Style/IfUnlessModifier</code> when multiline <code>if</code> that fits on one line and using implicit method call with hash value omission syntax. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13219">#13219</a>: Fix a false positive for <code>Style/ArgumentsForwarding</code> with Ruby 3.0 and optional position arguments. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13271">#13271</a>: Fix a false positive for <code>Lint/AmbiguousRange</code> when using rational literals. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13260">#13260</a>: Fix a false positive for <code>Lint/RedundantSafeNavigation</code> with namespaced constants. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13224">#13224</a>: Fix false positives for <code>Style/OperatorMethodCall</code> with named forwarding. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13213">#13213</a>: Fix false positives for <code>Style/AccessModifierDeclarations</code> when <code>AllowModifiersOnAttrs: true</code> and using splat with a percent symbol array, or with a constant. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13145">#13145</a>: Fix false positives for <code>Style/RedundantLineContinuation</code> when line continuations with comparison operator and the LHS is wrapped in parentheses. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/12875">#12875</a>: Fix false positive for <code>Style/ArgumentsForwarding</code> when argument is used inside a block. ([<a href="https://github.com/dvandersluis"><code>@dvandersluis</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13239">#13239</a>: Fix false positive for <code>Style/CollectionCompact</code> when using <code>delete_if</code>. ([<a href="https://github.com/masato-bkn"><code>@masato-bkn</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13210">#13210</a>: Fix omit_parentheses style for pattern match with value omission in single-line branch. ([<a href="https://github.com/gsamokovarov"><code>@gsamokovarov</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13149">#13149</a>: Handle crashes in custom Ruby extractors more gracefully. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13319">#13319</a>: Handle literal forward slashes inside a <code>regexp</code> in <code>Lint/LiteralInInterpolation</code>. ([<a href="https://github.com/dvandersluis"><code>@dvandersluis</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13208">#13208</a>: Fix an incorrect autocorrect for <code>Style/IfWithSemicolon</code> when single-line <code>if/;/end</code> when the then body contains a method call with <code>[]</code> or <code>[]=</code>. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13318">#13318</a>: Prevent modifying blocks with <code>Style/HashEachMethods</code> if the hash is modified within the block. ([<a href="https://github.com/dvandersluis"><code>@dvandersluis</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13293">#13293</a>: Fix <code>TargetRubyVersion</code> from a gemspec when the gemspec is not named like the folder it is located in. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13211">#13211</a>: Fix wrong autocorrect for <code>Style/GuardClause</code> when using heredoc without <code>else</code> branch. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13215">#13215</a>: Fix wrong autocorrect for <code>Lint/BigDecimalNew</code> when using <code>::BigDecimal.new</code>. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13215">#13215</a>: Fix wrong autocorrect for <code>Style/MethodCallWithArgsParentheses</code> with <code>EnforcedStyle: omit_parentheses</code> and whitespace. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13302">#13302</a>: Fix incompatible autocorrect between <code>Style/RedundantBegin</code> and <code>Style/BlockDelimiters</code> with <code>EnforcedStyle: braces_for_chaining</code>. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> </ul> <h3>Changes</h3> <ul> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13221">#13221</a>: Do not group accessors having RBS::Inline annotation comments in <code>Style/AccessorGrouping</code>. ([<a href="https://github.com/tk0miya"><code>@tk0miya</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13286">#13286</a>: Add <code>AllowedMethods</code> configuration to <code>Layout/FirstMethodArgumentLineBreak</code>. ([<a href="https://github.com/dvandersluis"><code>@dvandersluis</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/issues/13110">#13110</a>: Add support in <code>Style/ArgumentsForwarding</code> for detecting forwarding of all anonymous arguments. ([<a href="https://github.com/dvandersluis"><code>@dvandersluis</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13222">#13222</a>: Allow to write RBS::Inline annotation comments after method definition in <code>Style/CommentedKeyword</code>. ([<a href="https://github.com/tk0miya"><code>@tk0miya</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13253">#13253</a>: Emit a deprecation when custom cops inherit from <code>RuboCop::Cop::Cop</code>. ([<a href="https://github.com/earlopain"><code>@earlopain</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13300">#13300</a>: Set <code>EnforcedShorthandSyntax: either</code> by default for <code>Style/HashSyntax</code>. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> <li><a href="https://redirect.github.com/rubocop/rubocop/pull/13254">#13254</a>: Enhance the autocorrect for <code>Naming/InclusiveLanguage</code> when a sole suggestion is set. ([<a href="https://github.com/koic"><code>@koic</code></a>][])</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="c108ac0822
"><code>c108ac0</code></a> Cut 1.67</li> <li><a href="74ddd9bb96
"><code>74ddd9b</code></a> Update Changelog</li> <li><a href="b5c12aea89
"><code>b5c12ae</code></a> Update Changelog</li> <li><a href="c936160665
"><code>c936160</code></a> [Fix <a href="https://redirect.github.com/rubocop/rubocop/issues/13331">#13331</a>] Fix an error when using release task</li> <li><a href="60ecb00dd7
"><code>60ecb00</code></a> [Fix <a href="https://redirect.github.com/rubocop/rubocop/issues/13328">#13328</a>] Declare <code>Enabled</code> as a common config key</li> <li><a href="99022d9880
"><code>99022d9</code></a> Merge pull request <a href="https://redirect.github.com/rubocop/rubocop/issues/13327">#13327</a> from koic/make_server_mode_aware_of_auto_restart_fo...</li> <li><a href="60432f5e44
"><code>60432f5</code></a> Apply <code>RESTRICT_ON_SEND</code> to <code>Bundler/GemVersion</code></li> <li><a href="6b31c39f1e
"><code>6b31c39</code></a> Make server mode aware of auto-restart for .rubocop_todo.yml update</li> <li><a href="adb7ceed43
"><code>adb7cee</code></a> [Fix <a href="https://redirect.github.com/rubocop/rubocop/issues/9816">#9816</a>] Refine <code>Lint/SafeNavigationConsistency</code></li> <li><a href="7d6797cf9d
"><code>7d6797c</code></a> [Fix <a href="https://redirect.github.com/rubocop/rubocop/issues/13286">#13286</a>] Add <code>AllowedMethods</code> and <code>AllowedPatterns</code> configuration to `Lay...</li> <li>Additional commits viewable in <a href="https://github.com/rubocop/rubocop/compare/v1.66.1...v1.67.0">compare view</a></li> </ul> </details> <br /> [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rubocop&package-manager=bundler&previous-version=1.66.1&new-version=1.67.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details>
1470 lines
43 KiB
Ruby
1470 lines
43 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class TopicsController < ApplicationController
|
|
requires_login only: %i[
|
|
timings
|
|
destroy_timings
|
|
update
|
|
update_shared_draft
|
|
destroy
|
|
recover
|
|
status
|
|
invite
|
|
mute
|
|
unmute
|
|
set_notifications
|
|
move_posts
|
|
merge_topic
|
|
clear_pin
|
|
re_pin
|
|
status_update
|
|
timer
|
|
bulk
|
|
reset_new
|
|
change_post_owners
|
|
change_timestamps
|
|
archive_message
|
|
move_to_inbox
|
|
convert_topic
|
|
bookmark
|
|
publish
|
|
reset_bump_date
|
|
set_slow_mode
|
|
]
|
|
|
|
before_action :consider_user_for_promotion, only: :show
|
|
|
|
skip_before_action :check_xhr, only: %i[show feed]
|
|
|
|
def id_for_slug
|
|
topic = Topic.find_by_slug(params[:slug])
|
|
guardian.ensure_can_see!(topic)
|
|
raise Discourse::NotFound unless topic
|
|
render json: { slug: topic.slug, topic_id: topic.id, url: topic.url }
|
|
end
|
|
|
|
def show_by_external_id
|
|
topic = Topic.find_by(external_id: params[:external_id])
|
|
raise Discourse::NotFound unless topic
|
|
guardian.ensure_can_see!(topic)
|
|
redirect_to_correct_topic(topic, params[:post_number])
|
|
end
|
|
|
|
def show
|
|
if params[:id].is_a?(Array)
|
|
raise Discourse::InvalidParameters.new("Show only accepts a single ID")
|
|
end
|
|
|
|
flash["referer"] ||= request.referer[0..255] if request.referer
|
|
|
|
# TODO: We'd like to migrate the wordpress feed to another url. This keeps up backwards
|
|
# compatibility with existing installs.
|
|
return wordpress if params[:best].present?
|
|
|
|
# work around people somehow sending in arrays,
|
|
# arrays are not supported
|
|
params[:page] = begin
|
|
params[:page].to_i
|
|
rescue StandardError
|
|
1
|
|
end
|
|
|
|
opts =
|
|
params.slice(
|
|
:username_filters,
|
|
:filter,
|
|
:page,
|
|
:post_number,
|
|
:show_deleted,
|
|
:replies_to_post_number,
|
|
:filter_upwards_post_id,
|
|
:filter_top_level_replies,
|
|
)
|
|
username_filters = opts[:username_filters]
|
|
|
|
opts[:print] = true if params[:print] == "true"
|
|
opts[:username_filters] = username_filters.split(",") if username_filters.is_a?(String)
|
|
|
|
# Special case: a slug with a number in front should look by slug first before looking
|
|
# up that particular number
|
|
if params[:id] && params[:id] =~ /\A\d+[^\d\\]+\z/
|
|
topic = Topic.find_by_slug(params[:id])
|
|
return redirect_to_correct_topic(topic, opts[:post_number]) if topic
|
|
end
|
|
|
|
if opts[:print]
|
|
raise Discourse::InvalidAccess if SiteSetting.max_prints_per_hour_per_user.zero?
|
|
begin
|
|
unless @guardian.is_admin?
|
|
RateLimiter.new(
|
|
current_user,
|
|
"print-topic-per-hour",
|
|
SiteSetting.max_prints_per_hour_per_user,
|
|
1.hour,
|
|
).performed!
|
|
end
|
|
rescue RateLimiter::LimitExceeded
|
|
return render_json_error I18n.t("rate_limiter.slow_down")
|
|
end
|
|
end
|
|
|
|
begin
|
|
@topic_view = TopicView.new(params[:id] || params[:topic_id], current_user, opts)
|
|
rescue Discourse::NotFound => ex
|
|
if params[:id]
|
|
topic = Topic.find_by_slug(params[:id])
|
|
return redirect_to_correct_topic(topic, opts[:post_number]) if topic
|
|
end
|
|
|
|
raise ex
|
|
rescue Discourse::NotLoggedIn => ex
|
|
raise(SiteSetting.detailed_404 ? ex : Discourse::NotFound)
|
|
rescue Discourse::InvalidAccess => ex
|
|
# If the user can't see the topic, clean up notifications for it.
|
|
Notification.remove_for(current_user.id, params[:topic_id]) if current_user
|
|
|
|
deleted =
|
|
guardian.can_see_topic?(ex.obj, false) ||
|
|
(!guardian.can_see_topic?(ex.obj) && ex.obj&.access_topic_via_group && ex.obj.deleted_at)
|
|
|
|
if SiteSetting.detailed_404
|
|
if deleted
|
|
raise Discourse::NotFound.new(
|
|
"deleted topic",
|
|
custom_message: "deleted_topic",
|
|
status: 410,
|
|
check_permalinks: true,
|
|
original_path: ex.obj.relative_url,
|
|
)
|
|
elsif !guardian.can_see_topic?(ex.obj) && group = ex.obj&.access_topic_via_group
|
|
raise Discourse::InvalidAccess.new(
|
|
"not in group",
|
|
ex.obj,
|
|
custom_message: "not_in_group.title_topic",
|
|
custom_message_params: {
|
|
group: group.name,
|
|
},
|
|
group: serialize_data(group, BasicGroupSerializer, root: false),
|
|
)
|
|
end
|
|
|
|
raise ex
|
|
else
|
|
raise Discourse::NotFound.new(
|
|
nil,
|
|
check_permalinks: deleted,
|
|
original_path: ex.obj.relative_url,
|
|
)
|
|
end
|
|
end
|
|
|
|
page = params[:page]
|
|
if (page < 0) || ((page - 1) * @topic_view.chunk_size >= @topic_view.topic.highest_post_number)
|
|
raise Discourse::NotFound
|
|
end
|
|
|
|
discourse_expires_in 1.minute
|
|
|
|
if slugs_do_not_match || (!request.format.json? && params[:slug].nil?)
|
|
redirect_to_correct_topic(@topic_view.topic, opts[:post_number])
|
|
return
|
|
end
|
|
|
|
track_visit_to_topic
|
|
|
|
if should_track_visit_to_topic?
|
|
@topic_view.draft = Draft.get(current_user, @topic_view.draft_key, @topic_view.draft_sequence)
|
|
end
|
|
|
|
response.headers["X-Robots-Tag"] = "noindex" unless @topic_view.topic.visible
|
|
|
|
canonical_url UrlHelper.absolute_without_cdn(@topic_view.canonical_path)
|
|
|
|
# provide hint to crawlers only for now
|
|
# we would like to give them a bit more signal about age of data
|
|
if use_crawler_layout?
|
|
if last_modified = @topic_view.posts&.map { |p| p.updated_at }&.max&.httpdate
|
|
response.headers["Last-Modified"] = last_modified
|
|
end
|
|
end
|
|
|
|
perform_show_response
|
|
end
|
|
|
|
def publish
|
|
params.permit(:id, :destination_category_id)
|
|
|
|
topic = Topic.find(params[:id])
|
|
category = Category.find(params[:destination_category_id])
|
|
|
|
raise Discourse::InvalidParameters if category.id == SiteSetting.shared_drafts_category.to_i
|
|
|
|
guardian.ensure_can_publish_topic!(topic, category)
|
|
topic = TopicPublisher.new(topic, current_user, category.id).publish!
|
|
|
|
render_serialized(topic.reload, BasicTopicSerializer)
|
|
end
|
|
|
|
def wordpress
|
|
params.require(:best)
|
|
params.require(:topic_id)
|
|
params.permit(
|
|
:min_trust_level,
|
|
:min_score,
|
|
:min_replies,
|
|
:bypass_trust_level_score,
|
|
:only_moderator_liked,
|
|
)
|
|
|
|
begin
|
|
opts = {
|
|
best: params[:best].to_i,
|
|
min_trust_level: params[:min_trust_level] ? params[:min_trust_level].to_i : 1,
|
|
min_score: params[:min_score].to_i,
|
|
min_replies: params[:min_replies].to_i,
|
|
bypass_trust_level_score: params[:bypass_trust_level_score].to_i, # safe cause 0 means ignore
|
|
only_moderator_liked: params[:only_moderator_liked].to_s == "true",
|
|
exclude_hidden: true,
|
|
}
|
|
rescue NoMethodError
|
|
raise Discourse::InvalidParameters
|
|
end
|
|
|
|
@topic_view = TopicView.new(params[:topic_id], current_user, opts)
|
|
discourse_expires_in 1.minute
|
|
|
|
wordpress_serializer =
|
|
TopicViewWordpressSerializer.new(@topic_view, scope: guardian, root: false)
|
|
render_json_dump(wordpress_serializer)
|
|
end
|
|
|
|
def post_ids
|
|
params.require(:topic_id)
|
|
params.permit(:post_number, :username_filters, :filter)
|
|
|
|
options = {
|
|
filter_post_number: params[:post_number],
|
|
filter: params[:filter],
|
|
skip_limit: true,
|
|
asc: true,
|
|
skip_custom_fields: true,
|
|
}
|
|
|
|
fetch_topic_view(options)
|
|
render_json_dump(post_ids: @topic_view.posts.pluck(:id))
|
|
end
|
|
|
|
def posts
|
|
params.require(:topic_id)
|
|
params.permit(:post_ids, :post_number, :username_filters, :filter, :include_suggested)
|
|
|
|
include_suggested = params[:include_suggested] == "true"
|
|
|
|
options = {
|
|
filter_post_number: params[:post_number],
|
|
post_ids: params[:post_ids],
|
|
asc: ActiveRecord::Type::Boolean.new.deserialize(params[:asc]),
|
|
filter: params[:filter],
|
|
include_suggested: include_suggested,
|
|
include_related: include_suggested,
|
|
}
|
|
|
|
fetch_topic_view(options)
|
|
|
|
render_json_dump(
|
|
TopicViewPostsSerializer.new(
|
|
@topic_view,
|
|
scope: guardian,
|
|
root: false,
|
|
include_raw: !!params[:include_raw],
|
|
),
|
|
)
|
|
end
|
|
|
|
def excerpts
|
|
params.require(:topic_id)
|
|
params.require(:post_ids)
|
|
|
|
unless Array === params[:post_ids]
|
|
render_json_error("Expecting post_ids to contain a list of posts ids")
|
|
return
|
|
end
|
|
post_ids = params[:post_ids].map(&:to_i)
|
|
|
|
if post_ids.length > 100
|
|
render_json_error("Requested a chunk that is too big")
|
|
return
|
|
end
|
|
|
|
@topic = Topic.with_deleted.where(id: params[:topic_id]).first
|
|
guardian.ensure_can_see!(@topic)
|
|
|
|
@posts =
|
|
Post
|
|
.where(hidden: false, deleted_at: nil, topic_id: @topic.id)
|
|
.where("posts.id in (?)", post_ids)
|
|
.joins("LEFT JOIN users u on u.id = posts.user_id")
|
|
.pluck(:id, :cooked, :username, :action_code, :created_at)
|
|
.map do |post_id, cooked, username, action_code, created_at|
|
|
attrs = {
|
|
post_id: post_id,
|
|
username: username,
|
|
excerpt: PrettyText.excerpt(cooked, 800, keep_emoji_images: true),
|
|
}
|
|
|
|
if action_code
|
|
attrs[:action_code] = action_code
|
|
attrs[:created_at] = created_at
|
|
end
|
|
|
|
attrs
|
|
end
|
|
|
|
render json: @posts.to_json
|
|
end
|
|
|
|
def destroy_timings
|
|
topic_id = params[:topic_id].to_i
|
|
|
|
if params[:last].to_s == "1"
|
|
PostTiming.destroy_last_for(current_user, topic_id: topic_id)
|
|
else
|
|
PostTiming.destroy_for(current_user.id, [topic_id])
|
|
end
|
|
|
|
last_notification =
|
|
Notification
|
|
.where(user_id: current_user.id, topic_id: topic_id)
|
|
.order(created_at: :desc)
|
|
.limit(1)
|
|
.first
|
|
|
|
last_notification.update!(read: false) if last_notification
|
|
|
|
render body: nil
|
|
end
|
|
|
|
def update_shared_draft
|
|
topic = Topic.find_by(id: params[:id])
|
|
guardian.ensure_can_edit!(topic)
|
|
|
|
category = Category.find_by(id: params[:category_id].to_i)
|
|
guardian.ensure_can_publish_topic!(topic, category)
|
|
|
|
row_count = SharedDraft.where(topic_id: topic.id).update_all(category_id: category.id)
|
|
SharedDraft.create(topic_id: topic.id, category_id: category.id) if row_count == 0
|
|
|
|
render json: success_json
|
|
end
|
|
|
|
def update
|
|
topic = Topic.find_by(id: params[:topic_id])
|
|
guardian.ensure_can_edit!(topic)
|
|
|
|
if params[:category_id] && (params[:category_id].to_i != topic.category_id.to_i)
|
|
if topic.shared_draft
|
|
topic.shared_draft.update(category_id: params[:category_id])
|
|
params.delete(:category_id)
|
|
else
|
|
category = Category.find_by(id: params[:category_id])
|
|
|
|
if category || (params[:category_id].to_i == 0)
|
|
begin
|
|
guardian.ensure_can_move_topic_to_category!(category)
|
|
rescue Discourse::InvalidAccess
|
|
return(
|
|
render_json_error I18n.t("category.errors.move_topic_to_category_disallowed"),
|
|
status: :forbidden
|
|
)
|
|
end
|
|
else
|
|
return render_json_error(I18n.t("category.errors.not_found"))
|
|
end
|
|
|
|
if category &&
|
|
topic_tags = (params[:tags] || topic.tags.pluck(:name)).reject { |c| c.empty? }
|
|
if topic_tags.present?
|
|
allowed_tags =
|
|
DiscourseTagging.filter_allowed_tags(guardian, category: category).map(&:name)
|
|
|
|
invalid_tags = topic_tags - allowed_tags
|
|
|
|
# Do not raise an error on a topic's hidden tags when not modifying tags
|
|
if params[:tags].blank?
|
|
invalid_tags.each do |tag_name|
|
|
if DiscourseTagging.hidden_tag_names.include?(tag_name)
|
|
invalid_tags.delete(tag_name)
|
|
end
|
|
end
|
|
end
|
|
|
|
invalid_tags = Tag.where_name(invalid_tags).pluck(:name)
|
|
|
|
if !invalid_tags.empty?
|
|
if (invalid_tags & DiscourseTagging.hidden_tag_names).present?
|
|
return render_json_error(I18n.t("category.errors.disallowed_tags_generic"))
|
|
else
|
|
return(
|
|
render_json_error(
|
|
I18n.t("category.errors.disallowed_topic_tags", tags: invalid_tags.join(", ")),
|
|
)
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
changes = {}
|
|
|
|
PostRevisor.tracked_topic_fields.each_key { |f| changes[f] = params[f] if params.has_key?(f) }
|
|
|
|
changes.delete(:title) if topic.title == changes[:title]
|
|
changes.delete(:category_id) if topic.category_id.to_i == changes[:category_id].to_i
|
|
|
|
if Tag.include_tags?
|
|
topic_tags = topic.tags.map(&:name).sort
|
|
changes.delete(:tags) if changes[:tags]&.sort == topic_tags
|
|
end
|
|
|
|
success = true
|
|
|
|
if changes.length > 0
|
|
bypass_bump = should_bypass_bump?(changes)
|
|
|
|
first_post = topic.ordered_posts.first
|
|
success =
|
|
PostRevisor.new(first_post, topic).revise!(
|
|
current_user,
|
|
changes,
|
|
validate_post: false,
|
|
bypass_bump: bypass_bump,
|
|
keep_existing_draft: params[:keep_existing_draft].to_s == "true",
|
|
)
|
|
|
|
topic.errors.add(:base, :unable_to_update) if !success && topic.errors.blank?
|
|
end
|
|
|
|
# this is used to return the title to the client as it may have been changed by "TextCleaner"
|
|
success ? render_serialized(topic, BasicTopicSerializer) : render_json_error(topic)
|
|
end
|
|
|
|
def update_tags
|
|
params.require(:tags)
|
|
topic = Topic.find_by(id: params[:topic_id])
|
|
guardian.ensure_can_edit_tags!(topic)
|
|
|
|
success =
|
|
PostRevisor.new(topic.first_post, topic).revise!(
|
|
current_user,
|
|
{ tags: params[:tags] },
|
|
validate_post: false,
|
|
)
|
|
|
|
success ? render_serialized(topic, BasicTopicSerializer) : render_json_error(topic)
|
|
end
|
|
|
|
def feature_stats
|
|
params.require(:category_id)
|
|
category_id = params[:category_id].to_i
|
|
|
|
visible_topics = Topic.listable_topics.visible
|
|
|
|
render json: {
|
|
pinned_in_category_count:
|
|
visible_topics
|
|
.where(category_id: category_id)
|
|
.where(pinned_globally: false)
|
|
.where.not(pinned_at: nil)
|
|
.count,
|
|
pinned_globally_count:
|
|
visible_topics.where(pinned_globally: true).where.not(pinned_at: nil).count,
|
|
banner_count: Topic.listable_topics.where(archetype: Archetype.banner).count,
|
|
}
|
|
end
|
|
|
|
def status
|
|
params.require(:status)
|
|
params.require(:enabled)
|
|
params.permit(:until)
|
|
|
|
status = params[:status]
|
|
topic_id = params[:topic_id].to_i
|
|
enabled = params[:enabled] == "true"
|
|
|
|
check_for_status_presence(:status, status)
|
|
@topic =
|
|
if params[:category_id]
|
|
Topic.find_by(id: topic_id, category_id: params[:category_id].to_i)
|
|
else
|
|
Topic.find_by(id: topic_id)
|
|
end
|
|
|
|
status_opts = { until: params[:until].presence }
|
|
|
|
if status == "visible"
|
|
status_opts[:visibility_reason_id] = (
|
|
if enabled
|
|
Topic.visibility_reasons[:manually_relisted]
|
|
else
|
|
Topic.visibility_reasons[:manually_unlisted]
|
|
end
|
|
)
|
|
end
|
|
|
|
case status
|
|
when "closed"
|
|
guardian.ensure_can_close_topic!(@topic)
|
|
when "archived"
|
|
guardian.ensure_can_archive_topic!(@topic)
|
|
when "visible"
|
|
guardian.ensure_can_toggle_topic_visibility!(@topic)
|
|
when "pinned"
|
|
guardian.ensure_can_pin_unpin_topic!(@topic)
|
|
else
|
|
guardian.ensure_can_moderate!(@topic)
|
|
end
|
|
|
|
@topic.update_status(status, enabled, current_user, status_opts)
|
|
|
|
render json:
|
|
success_json.merge!(
|
|
topic_status_update:
|
|
TopicTimerSerializer.new(TopicTimer.find_by(topic: @topic), root: false),
|
|
)
|
|
end
|
|
|
|
def mute
|
|
toggle_mute
|
|
end
|
|
|
|
def unmute
|
|
toggle_mute
|
|
end
|
|
|
|
def timer
|
|
params.permit(:time, :based_on_last_post, :category_id)
|
|
params.require(:status_type)
|
|
|
|
status_type =
|
|
begin
|
|
TopicTimer.types.fetch(params[:status_type].to_sym)
|
|
rescue StandardError
|
|
invalid_param(:status_type)
|
|
end
|
|
based_on_last_post = params[:based_on_last_post]
|
|
params.require(:duration_minutes) if based_on_last_post
|
|
|
|
topic = Topic.find_by(id: params[:topic_id])
|
|
guardian.ensure_can_moderate!(topic)
|
|
|
|
guardian.ensure_can_delete!(topic) if TopicTimer.destructive_types.values.include?(status_type)
|
|
|
|
options = { by_user: current_user, based_on_last_post: based_on_last_post }
|
|
|
|
options.merge!(category_id: params[:category_id]) if !params[:category_id].blank?
|
|
if params[:duration_minutes].present?
|
|
options.merge!(duration_minutes: params[:duration_minutes].to_i)
|
|
end
|
|
options.merge!(duration: params[:duration].to_i) if params[:duration].present?
|
|
|
|
begin
|
|
topic_timer = topic.set_or_create_timer(status_type, params[:time], **options)
|
|
rescue ActiveRecord::RecordInvalid => e
|
|
return render_json_error(e.message)
|
|
end
|
|
|
|
if topic.save
|
|
render json:
|
|
success_json.merge!(
|
|
execute_at: topic_timer&.execute_at,
|
|
duration_minutes: topic_timer&.duration_minutes,
|
|
based_on_last_post: topic_timer&.based_on_last_post,
|
|
closed: topic.closed,
|
|
category_id: topic_timer&.category_id,
|
|
)
|
|
else
|
|
render_json_error(topic)
|
|
end
|
|
end
|
|
|
|
def make_banner
|
|
topic = Topic.find_by(id: params[:topic_id].to_i)
|
|
guardian.ensure_can_banner_topic!(topic)
|
|
|
|
topic.make_banner!(current_user)
|
|
|
|
render body: nil
|
|
end
|
|
|
|
def remove_banner
|
|
topic = Topic.find_by(id: params[:topic_id].to_i)
|
|
guardian.ensure_can_banner_topic!(topic)
|
|
|
|
topic.remove_banner!(current_user)
|
|
|
|
render body: nil
|
|
end
|
|
|
|
def remove_bookmarks
|
|
topic = Topic.find(params[:topic_id].to_i)
|
|
BookmarkManager.new(current_user).destroy_for_topic(topic)
|
|
render body: nil
|
|
end
|
|
|
|
def archive_message
|
|
toggle_archive_message(true)
|
|
end
|
|
|
|
def move_to_inbox
|
|
toggle_archive_message(false)
|
|
end
|
|
|
|
def toggle_archive_message(archive)
|
|
topic = Topic.find(params[:id].to_i)
|
|
|
|
group_id = nil
|
|
|
|
group_ids = current_user.groups.pluck(:id)
|
|
if group_ids.present?
|
|
allowed_groups =
|
|
topic.allowed_groups.where("topic_allowed_groups.group_id IN (?)", group_ids).pluck(:id)
|
|
|
|
allowed_groups.each do |id|
|
|
if archive
|
|
GroupArchivedMessage.archive!(id, topic, acting_user_id: current_user.id)
|
|
|
|
group_id = id
|
|
else
|
|
GroupArchivedMessage.move_to_inbox!(id, topic, acting_user_id: current_user.id)
|
|
end
|
|
end
|
|
end
|
|
|
|
if topic.allowed_users.include?(current_user)
|
|
if archive
|
|
UserArchivedMessage.archive!(current_user.id, topic)
|
|
else
|
|
UserArchivedMessage.move_to_inbox!(current_user.id, topic)
|
|
end
|
|
end
|
|
|
|
if group_id
|
|
name = Group.find_by(id: group_id).try(:name)
|
|
render_json_dump(group_name: name)
|
|
else
|
|
render body: nil
|
|
end
|
|
end
|
|
|
|
def bookmark
|
|
topic = Topic.find(params[:topic_id].to_i)
|
|
|
|
bookmark_manager = BookmarkManager.new(current_user)
|
|
bookmark_manager.create_for(bookmarkable_id: topic.id, bookmarkable_type: "Topic")
|
|
|
|
return render_json_error(bookmark_manager, status: 400) if bookmark_manager.errors.any?
|
|
|
|
render body: nil
|
|
end
|
|
|
|
def destroy
|
|
topic = Topic.with_deleted.find_by(id: params[:id])
|
|
force_destroy = ActiveModel::Type::Boolean.new.cast(params[:force_destroy])
|
|
|
|
if force_destroy
|
|
if !topic
|
|
raise Discourse::InvalidAccess
|
|
elsif !guardian.can_permanently_delete?(topic)
|
|
return render_json_error topic.cannot_permanently_delete_reason(current_user), status: 403
|
|
end
|
|
else
|
|
guardian.ensure_can_delete!(topic)
|
|
end
|
|
|
|
PostDestroyer.new(
|
|
current_user,
|
|
topic.ordered_posts.with_deleted.first,
|
|
context: params[:context],
|
|
force_destroy: force_destroy,
|
|
).destroy
|
|
|
|
render body: nil
|
|
rescue Discourse::InvalidAccess
|
|
render_json_error I18n.t("delete_topic_failed")
|
|
end
|
|
|
|
def recover
|
|
topic = Topic.where(id: params[:topic_id]).with_deleted.first
|
|
guardian.ensure_can_recover_topic!(topic)
|
|
|
|
first_post = topic.posts.with_deleted.order(:post_number).first
|
|
PostDestroyer.new(current_user, first_post, context: params[:context]).recover
|
|
|
|
render body: nil
|
|
end
|
|
|
|
def excerpt
|
|
render body: nil
|
|
end
|
|
|
|
def remove_allowed_user
|
|
params.require(:username)
|
|
topic = Topic.find_by(id: params[:topic_id])
|
|
raise Discourse::NotFound unless topic
|
|
user = User.find_by(username: params[:username])
|
|
raise Discourse::NotFound unless user
|
|
|
|
guardian.ensure_can_remove_allowed_users!(topic, user)
|
|
|
|
if topic.remove_allowed_user(current_user, user)
|
|
render json: success_json
|
|
else
|
|
render json: failed_json, status: 422
|
|
end
|
|
end
|
|
|
|
def remove_allowed_group
|
|
params.require(:name)
|
|
topic = Topic.find_by(id: params[:topic_id])
|
|
guardian.ensure_can_remove_allowed_users!(topic)
|
|
|
|
if topic.remove_allowed_group(current_user, params[:name])
|
|
render json: success_json
|
|
else
|
|
render json: failed_json, status: 422
|
|
end
|
|
end
|
|
|
|
def invite_group
|
|
group = Group.find_by(name: params[:group])
|
|
raise Discourse::NotFound if !group
|
|
|
|
topic = Topic.find_by(id: params[:topic_id])
|
|
raise Discourse::NotFound if !topic
|
|
|
|
if !pm_has_slots?(topic)
|
|
return(
|
|
render_json_error(
|
|
I18n.t(
|
|
"pm_reached_recipients_limit",
|
|
recipients_limit: SiteSetting.max_allowed_message_recipients,
|
|
),
|
|
)
|
|
)
|
|
end
|
|
|
|
if topic.private_message?
|
|
guardian.ensure_can_invite_group_to_private_message!(group, topic)
|
|
topic.invite_group(current_user, group)
|
|
render_json_dump BasicGroupSerializer.new(group, scope: guardian, root: "group")
|
|
else
|
|
render json: failed_json, status: 422
|
|
end
|
|
end
|
|
|
|
def invite
|
|
topic = Topic.find_by(id: params[:topic_id])
|
|
raise Discourse::NotFound if !topic
|
|
|
|
return render_json_error(I18n.t("topic_invite.not_pm")) if !topic.private_message?
|
|
|
|
if !pm_has_slots?(topic)
|
|
return(
|
|
render_json_error(
|
|
I18n.t(
|
|
"pm_reached_recipients_limit",
|
|
recipients_limit: SiteSetting.max_allowed_message_recipients,
|
|
),
|
|
)
|
|
)
|
|
end
|
|
|
|
guardian.ensure_can_invite_to!(topic)
|
|
|
|
username_or_email = params[:user] ? fetch_username : fetch_email
|
|
group_ids =
|
|
Group.lookup_groups(group_ids: params[:group_ids], group_names: params[:group_names]).pluck(
|
|
:id,
|
|
)
|
|
|
|
begin
|
|
if topic.invite(current_user, username_or_email, group_ids, params[:custom_message])
|
|
if user = User.find_by_username_or_email(username_or_email)
|
|
render_json_dump BasicUserSerializer.new(user, scope: guardian, root: "user")
|
|
else
|
|
render json: success_json
|
|
end
|
|
else
|
|
json = failed_json
|
|
|
|
unless topic.private_message?
|
|
group_names =
|
|
topic
|
|
.category
|
|
.visible_group_names(current_user)
|
|
.where(automatic: false)
|
|
.pluck(:name)
|
|
.join(", ")
|
|
|
|
if group_names.present?
|
|
json.merge!(errors: [I18n.t("topic_invite.failed_to_invite", group_names: group_names)])
|
|
end
|
|
end
|
|
|
|
render json: json, status: 422
|
|
end
|
|
rescue Topic::UserExists, Topic::NotAllowed => e
|
|
render json: { errors: [e.message] }, status: 422
|
|
end
|
|
end
|
|
|
|
def set_notifications
|
|
user =
|
|
if is_api? && @guardian.is_admin? &&
|
|
(params[:username].present? || params[:external_id].present?)
|
|
fetch_user_from_params
|
|
else
|
|
current_user
|
|
end
|
|
|
|
topic = Topic.find(params[:topic_id].to_i)
|
|
TopicUser.change(user, topic.id, notification_level: params[:notification_level].to_i)
|
|
render json: success_json
|
|
end
|
|
|
|
def merge_topic
|
|
topic_id = params.require(:topic_id)
|
|
destination_topic_id = params.require(:destination_topic_id)
|
|
params.permit(:participants)
|
|
params.permit(:chronological_order)
|
|
params.permit(:archetype)
|
|
|
|
raise Discourse::InvalidAccess if params[:archetype] == "private_message" && !guardian.is_staff?
|
|
|
|
topic = Topic.find_by(id: topic_id)
|
|
guardian.ensure_can_move_posts!(topic)
|
|
|
|
destination_topic = Topic.find_by(id: destination_topic_id)
|
|
guardian.ensure_can_create_post_on_topic!(destination_topic)
|
|
|
|
args = {}
|
|
args[:destination_topic_id] = destination_topic_id.to_i
|
|
args[:chronological_order] = params[:chronological_order] == "true"
|
|
|
|
if params[:archetype].present?
|
|
args[:archetype] = params[:archetype]
|
|
args[:participants] = params[:participants] if params[:participants].present? &&
|
|
params[:archetype] == "private_message"
|
|
end
|
|
|
|
acting_user = current_user
|
|
hijack(info: "merging topic #{topic_id.inspect} into #{destination_topic_id.inspect}") do
|
|
destination_topic = topic.move_posts(acting_user, topic.posts.pluck(:id), args)
|
|
render_topic_changes(destination_topic)
|
|
end
|
|
end
|
|
|
|
def move_posts
|
|
post_ids = params.require(:post_ids)
|
|
topic_id = params.require(:topic_id)
|
|
params.permit(:category_id)
|
|
params.permit(:tags)
|
|
params.permit(:participants)
|
|
params.permit(:chronological_order)
|
|
params.permit(:archetype)
|
|
|
|
topic = Topic.with_deleted.find_by(id: topic_id)
|
|
guardian.ensure_can_move_posts!(topic)
|
|
|
|
if params[:title].present?
|
|
# when creating a new topic, ensure the 1st post is a regular post
|
|
if Post.where(topic: topic, id: post_ids).order(:post_number).pick(:post_type) !=
|
|
Post.types[:regular]
|
|
return(
|
|
render_json_error(
|
|
"When moving posts to a new topic, the first post must be a regular post.",
|
|
)
|
|
)
|
|
end
|
|
|
|
if params[:category_id].present?
|
|
guardian.ensure_can_create_topic_on_category!(params[:category_id])
|
|
end
|
|
end
|
|
|
|
destination_topic = move_posts_to_destination(topic)
|
|
render_topic_changes(destination_topic)
|
|
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => ex
|
|
render_json_error(ex)
|
|
end
|
|
|
|
def change_post_owners
|
|
params.require(:post_ids)
|
|
params.require(:topic_id)
|
|
params.require(:username)
|
|
|
|
guardian.ensure_can_change_post_owner!
|
|
|
|
begin
|
|
PostOwnerChanger.new(
|
|
post_ids: params[:post_ids].to_a,
|
|
topic_id: params[:topic_id].to_i,
|
|
new_owner: User.find_by(username: params[:username]),
|
|
acting_user: current_user,
|
|
).change_owner!
|
|
render json: success_json
|
|
rescue ArgumentError
|
|
render json: failed_json, status: 422
|
|
end
|
|
end
|
|
|
|
def change_timestamps
|
|
topic_id = params.require(:topic_id).to_i
|
|
timestamp = params.require(:timestamp).to_f
|
|
|
|
guardian.ensure_can_change_post_timestamps!
|
|
|
|
topic = Topic.with_deleted.find(topic_id)
|
|
previous_timestamp = topic.first_post.created_at
|
|
|
|
begin
|
|
TopicTimestampChanger.new(topic: topic, timestamp: timestamp).change!
|
|
|
|
StaffActionLogger.new(current_user).log_topic_timestamps_changed(
|
|
topic,
|
|
Time.zone.at(timestamp),
|
|
previous_timestamp,
|
|
)
|
|
|
|
render json: success_json
|
|
rescue ActiveRecord::RecordInvalid, TopicTimestampChanger::InvalidTimestampError
|
|
render json: failed_json, status: 422
|
|
end
|
|
end
|
|
|
|
def clear_pin
|
|
topic = Topic.find_by(id: params[:topic_id].to_i)
|
|
guardian.ensure_can_see!(topic)
|
|
topic.clear_pin_for(current_user)
|
|
render body: nil
|
|
end
|
|
|
|
def re_pin
|
|
topic = Topic.find_by(id: params[:topic_id].to_i)
|
|
guardian.ensure_can_see!(topic)
|
|
topic.re_pin_for(current_user)
|
|
render body: nil
|
|
end
|
|
|
|
def timings
|
|
allowed_params = topic_params
|
|
|
|
topic_id = allowed_params[:topic_id].to_i
|
|
topic_time = allowed_params[:topic_time].to_i
|
|
timings = allowed_params[:timings].to_h || {}
|
|
|
|
# ensure we capture current user for the block
|
|
user = current_user
|
|
|
|
hijack do
|
|
PostTiming.process_timings(
|
|
user,
|
|
topic_id,
|
|
topic_time,
|
|
timings.map { |post_number, t| [post_number.to_i, t.to_i] },
|
|
mobile: view_context.mobile_view?,
|
|
)
|
|
render body: nil
|
|
end
|
|
end
|
|
|
|
def feed
|
|
raise Discourse::NotFound if !Post.exists?(topic_id: params[:topic_id])
|
|
|
|
begin
|
|
@topic_view = TopicView.new(params[:topic_id])
|
|
rescue Discourse::NotLoggedIn
|
|
raise Discourse::NotFound
|
|
rescue Discourse::InvalidAccess => ex
|
|
deleted =
|
|
guardian.can_see_topic?(ex.obj, false) ||
|
|
(!guardian.can_see_topic?(ex.obj) && ex.obj&.access_topic_via_group && ex.obj.deleted_at)
|
|
|
|
raise Discourse::NotFound.new(
|
|
nil,
|
|
check_permalinks: deleted,
|
|
original_path: ex.obj.relative_url,
|
|
)
|
|
end
|
|
|
|
discourse_expires_in 1.minute
|
|
|
|
response.headers["X-Robots-Tag"] = "noindex, nofollow"
|
|
render "topics/show", formats: [:rss]
|
|
end
|
|
|
|
def bulk
|
|
if params[:topic_ids].present?
|
|
unless Array === params[:topic_ids]
|
|
raise Discourse::InvalidParameters.new("Expecting topic_ids to contain a list of topic ids")
|
|
end
|
|
topic_ids = params[:topic_ids].map { |t| t.to_i }
|
|
elsif params[:filter] == "unread"
|
|
topic_ids = bulk_unread_topic_ids
|
|
else
|
|
raise ActionController::ParameterMissing.new(:topic_ids)
|
|
end
|
|
|
|
operation =
|
|
params
|
|
.require(:operation)
|
|
.permit(
|
|
:type,
|
|
:group,
|
|
:category_id,
|
|
:notification_level_id,
|
|
:message,
|
|
*DiscoursePluginRegistry.permitted_bulk_action_parameters,
|
|
tags: [],
|
|
)
|
|
.to_h
|
|
.symbolize_keys
|
|
|
|
raise ActionController::ParameterMissing.new(:operation_type) if operation[:type].blank?
|
|
|
|
operator = TopicsBulkAction.new(current_user, topic_ids, operation, group: operation[:group])
|
|
hijack(info: "topics bulk action #{operation[:type]}") do
|
|
changed_topic_ids = operator.perform!
|
|
render_json_dump topic_ids: changed_topic_ids
|
|
end
|
|
end
|
|
|
|
def private_message_reset_new
|
|
topic_query = TopicQuery.new(current_user, limit: false)
|
|
|
|
if params[:topic_ids].present?
|
|
unless Array === params[:topic_ids]
|
|
raise Discourse::InvalidParameters.new("Expecting topic_ids to contain a list of topic ids")
|
|
end
|
|
|
|
topic_scope =
|
|
topic_query.private_messages_for(current_user, :all).where(
|
|
"topics.id IN (?)",
|
|
params[:topic_ids].map(&:to_i),
|
|
)
|
|
else
|
|
params.require(:inbox)
|
|
inbox = params[:inbox].to_s
|
|
filter = private_message_filter(topic_query, inbox)
|
|
topic_scope = topic_query.filter_private_message_new(current_user, filter)
|
|
end
|
|
|
|
topic_ids =
|
|
TopicsBulkAction.new(current_user, topic_scope.pluck(:id), type: "dismiss_topics").perform!
|
|
|
|
render json: success_json.merge(topic_ids: topic_ids)
|
|
end
|
|
|
|
def reset_new
|
|
topic_scope =
|
|
if current_user.new_new_view_enabled?
|
|
if (params[:dismiss_topics] && params[:dismiss_posts])
|
|
TopicQuery.new(current_user).new_and_unread_results(limit: false)
|
|
elsif params[:dismiss_topics]
|
|
TopicQuery.new(current_user).new_results(limit: false)
|
|
elsif params[:dismiss_posts]
|
|
TopicQuery.new(current_user).unread_results(limit: false)
|
|
else
|
|
Topic.none
|
|
end
|
|
else
|
|
TopicQuery.new(current_user).new_results(limit: false)
|
|
end
|
|
if tag_name = params[:tag_id]
|
|
tag_name = DiscourseTagging.visible_tags(guardian).where(name: tag_name).pluck(:name).first
|
|
end
|
|
|
|
topic_scope =
|
|
if params[:category_id].present?
|
|
category_id = params[:category_id].to_i
|
|
|
|
category_ids =
|
|
if ActiveModel::Type::Boolean.new.cast(params[:include_subcategories])
|
|
Category.subcategory_ids(category_id)
|
|
else
|
|
[category_id]
|
|
end
|
|
|
|
category_ids &= guardian.allowed_category_ids
|
|
if category_ids.blank?
|
|
scope = topic_scope.none
|
|
else
|
|
scope = topic_scope.where(category_id: category_ids)
|
|
scope = scope.joins(:tags).where(tags: { name: tag_name }) if tag_name
|
|
end
|
|
scope
|
|
elsif tag_name.present?
|
|
topic_scope.joins(:tags).where(tags: { name: tag_name })
|
|
else
|
|
if params[:tracked].to_s == "true"
|
|
TopicQuery.tracked_filter(topic_scope, current_user.id)
|
|
else
|
|
current_user.user_stat.update_column(:new_since, Time.zone.now)
|
|
topic_scope
|
|
end
|
|
end
|
|
|
|
if params[:topic_ids].present?
|
|
unless Array === params[:topic_ids]
|
|
raise Discourse::InvalidParameters.new("Expecting topic_ids to contain a list of topic ids")
|
|
end
|
|
|
|
topic_ids = params[:topic_ids].map(&:to_i)
|
|
topic_scope = topic_scope.where(id: topic_ids)
|
|
end
|
|
|
|
dismissed_topic_ids = []
|
|
dismissed_post_topic_ids = []
|
|
|
|
if !current_user.new_new_view_enabled? || params[:dismiss_topics]
|
|
dismissed_topic_ids =
|
|
TopicsBulkAction.new(current_user, topic_scope.pluck(:id), type: "dismiss_topics").perform!
|
|
end
|
|
|
|
if params[:dismiss_posts]
|
|
if params[:untrack]
|
|
dismissed_post_topic_ids =
|
|
TopicsBulkAction.new(
|
|
current_user,
|
|
topic_scope.pluck(:id),
|
|
type: "change_notification_level",
|
|
notification_level_id: NotificationLevels.topic_levels[:regular],
|
|
).perform!
|
|
else
|
|
dismissed_post_topic_ids =
|
|
TopicsBulkAction.new(current_user, topic_scope.pluck(:id), type: "dismiss_posts").perform!
|
|
end
|
|
end
|
|
|
|
render_json_dump topic_ids: dismissed_topic_ids.concat(dismissed_post_topic_ids).uniq
|
|
end
|
|
|
|
def convert_topic
|
|
params.require(:id)
|
|
params.require(:type)
|
|
|
|
topic = Topic.find_by(id: params[:id])
|
|
guardian.ensure_can_convert_topic!(topic)
|
|
|
|
topic =
|
|
if params[:type] == "public"
|
|
topic.convert_to_public_topic(current_user, category_id: params[:category_id])
|
|
else
|
|
topic.convert_to_private_message(current_user)
|
|
end
|
|
|
|
topic.valid? ? render_topic_changes(topic) : render_json_error(topic)
|
|
end
|
|
|
|
def reset_bump_date
|
|
params.require(:id)
|
|
params.permit(:post_id)
|
|
|
|
guardian.ensure_can_update_bumped_at!
|
|
|
|
topic = Topic.find_by(id: params[:id])
|
|
raise Discourse::NotFound.new unless topic
|
|
|
|
topic.reset_bumped_at(params[:post_id])
|
|
render body: nil
|
|
end
|
|
|
|
def set_slow_mode
|
|
topic = Topic.find(params[:topic_id])
|
|
slow_mode_type = TopicTimer.types[:clear_slow_mode]
|
|
timer = TopicTimer.find_by(topic: topic, status_type: slow_mode_type)
|
|
|
|
guardian.ensure_can_moderate!(topic)
|
|
topic.update!(slow_mode_seconds: params[:seconds])
|
|
enabled = params[:seconds].to_i > 0
|
|
|
|
time = enabled && params[:enabled_until].present? ? params[:enabled_until] : nil
|
|
|
|
topic.set_or_create_timer(slow_mode_type, time, by_user: timer&.user)
|
|
|
|
StaffActionLogger.new(current_user).log_topic_slow_mode(
|
|
topic,
|
|
enabled:,
|
|
seconds: params[:seconds],
|
|
until: time,
|
|
)
|
|
|
|
head :ok
|
|
end
|
|
|
|
private
|
|
|
|
def topic_params
|
|
params.permit(:topic_id, :topic_time, timings: {})
|
|
end
|
|
|
|
def fetch_topic_view(options)
|
|
if (username_filters = params[:username_filters]).present?
|
|
options[:username_filters] = username_filters.split(",")
|
|
end
|
|
|
|
@topic_view = TopicView.new(params[:topic_id], current_user, options)
|
|
end
|
|
|
|
def toggle_mute
|
|
@topic = Topic.find_by(id: params[:topic_id].to_i)
|
|
guardian.ensure_can_see!(@topic)
|
|
|
|
@topic.toggle_mute(current_user)
|
|
render body: nil
|
|
end
|
|
|
|
def consider_user_for_promotion
|
|
Promotion.new(current_user).review if current_user.present?
|
|
end
|
|
|
|
def should_bypass_bump?(changes)
|
|
(changes[:category_id].present? && SiteSetting.disable_category_edit_notifications) ||
|
|
(changes[:tags].present? && SiteSetting.disable_tags_edit_notifications)
|
|
end
|
|
|
|
def slugs_do_not_match
|
|
if SiteSetting.slug_generation_method != "encoded"
|
|
params[:slug] && @topic_view.topic.slug != params[:slug]
|
|
else
|
|
params[:slug] && CGI.unescape(@topic_view.topic.slug) != params[:slug]
|
|
end
|
|
end
|
|
|
|
def redirect_to_correct_topic(topic, post_number = nil)
|
|
begin
|
|
guardian.ensure_can_see!(topic)
|
|
rescue Discourse::InvalidAccess => ex
|
|
raise(SiteSetting.detailed_404 ? ex : Discourse::NotFound)
|
|
end
|
|
|
|
# Allow plugins to append allowed query parameters, so they aren't scrubbed on redirect to proper topic URL
|
|
additional_allowed_query_parameters =
|
|
DiscoursePluginRegistry.apply_modifier(
|
|
:redirect_to_correct_topic_additional_query_parameters,
|
|
[],
|
|
)
|
|
|
|
opts =
|
|
params.slice(
|
|
*%i[page print filter_top_level_replies preview_theme_id].concat(
|
|
additional_allowed_query_parameters,
|
|
),
|
|
)
|
|
opts.delete(:page) if params[:page] == 0
|
|
|
|
url = topic.relative_url
|
|
url << "/#{post_number}" if post_number.to_i > 0
|
|
url << ".json" if request.format.json?
|
|
|
|
opts.each do |k, v|
|
|
s = url.include?("?") ? "&" : "?"
|
|
url << "#{s}#{k}=#{v}"
|
|
end
|
|
|
|
redirect_to url, status: 301
|
|
end
|
|
|
|
def track_visit_to_topic
|
|
topic_id = @topic_view.topic.id
|
|
ip = request.remote_ip
|
|
user_id = (current_user.id if current_user)
|
|
|
|
if !request.format.json?
|
|
hash = {
|
|
referer: request.referer || flash[:referer],
|
|
host: request.host,
|
|
current_user: current_user,
|
|
topic_id: @topic_view.topic.id,
|
|
post_number: @topic_view.current_post_number,
|
|
username: request["u"],
|
|
ip_address: request.remote_ip,
|
|
}
|
|
# defer this way so we do not capture the whole controller
|
|
# in the closure
|
|
TopicsController.defer_add_incoming_link(hash)
|
|
end
|
|
|
|
TopicsController.defer_track_visit(topic_id, user_id) if should_track_visit_to_topic?
|
|
end
|
|
|
|
def self.defer_track_visit(topic_id, user_id)
|
|
Scheduler::Defer.later "Track Visit" do
|
|
TopicUser.track_visit!(topic_id, user_id)
|
|
end
|
|
end
|
|
|
|
def self.defer_topic_view(topic_id, ip, user_id = nil)
|
|
Scheduler::Defer.later "Topic View" do
|
|
topic = Topic.find_by(id: topic_id)
|
|
next if topic.blank?
|
|
next if topic.shared_draft?
|
|
|
|
# We need to make sure that we aren't allowing recording
|
|
# random topic views against topics the user cannot see.
|
|
user = User.find_by(id: user_id) if user_id.present?
|
|
next if user_id.present? && user.blank?
|
|
next if !Guardian.new(user).can_see_topic?(topic)
|
|
|
|
TopicViewItem.add(topic_id, ip, user_id)
|
|
end
|
|
end
|
|
|
|
def self.defer_add_incoming_link(hash)
|
|
Scheduler::Defer.later "Track Link" do
|
|
IncomingLink.add(hash)
|
|
end
|
|
end
|
|
|
|
def should_track_visit_to_topic?
|
|
!!((!request.format.json? || params[:track_visit]) && current_user)
|
|
end
|
|
|
|
def perform_show_response
|
|
if request.head?
|
|
head :ok
|
|
return
|
|
end
|
|
|
|
if params[:replies_to_post_number] || params[:filter_upwards_post_id] ||
|
|
params[:filter_top_level_replies] || @topic_view.next_page.present?
|
|
@topic_view.include_suggested = false
|
|
@topic_view.include_related = false
|
|
end
|
|
|
|
topic_view_serializer =
|
|
TopicViewSerializer.new(
|
|
@topic_view,
|
|
scope: guardian,
|
|
root: false,
|
|
include_raw: !!params[:include_raw],
|
|
)
|
|
|
|
respond_to do |format|
|
|
format.html do
|
|
@tags = SiteSetting.tagging_enabled ? @topic_view.topic.tags.visible(guardian) : []
|
|
@breadcrumbs = helpers.categories_breadcrumb(@topic_view.topic) || []
|
|
@description_meta =
|
|
@topic_view.topic.excerpt.present? ? @topic_view.topic.excerpt : @topic_view.summary
|
|
store_preloaded("topic_#{@topic_view.topic.id}", MultiJson.dump(topic_view_serializer))
|
|
render :show
|
|
end
|
|
|
|
format.json { render_json_dump(topic_view_serializer) }
|
|
end
|
|
end
|
|
|
|
def render_topic_changes(dest_topic)
|
|
if dest_topic.present?
|
|
render json: { success: true, url: dest_topic.relative_url }
|
|
else
|
|
render json: { success: false }
|
|
end
|
|
end
|
|
|
|
def move_posts_to_destination(topic)
|
|
args = {}
|
|
args[:title] = params[:title] if params[:title].present?
|
|
args[:destination_topic_id] = params[:destination_topic_id].to_i if params[
|
|
:destination_topic_id
|
|
].present?
|
|
args[:tags] = params[:tags] if params[:tags].present?
|
|
args[:chronological_order] = params[:chronological_order] == "true"
|
|
|
|
if params[:archetype].present?
|
|
args[:archetype] = params[:archetype]
|
|
args[:participants] = params[:participants] if params[:participants].present? &&
|
|
params[:archetype] == "private_message"
|
|
else
|
|
args[:category_id] = params[:category_id].to_i if params[:category_id].present?
|
|
end
|
|
|
|
topic.move_posts(current_user, post_ids_including_replies, args)
|
|
end
|
|
|
|
def check_for_status_presence(key, attr)
|
|
invalid_param(key) if %w[pinned pinned_globally visible closed archived].exclude?(attr)
|
|
end
|
|
|
|
def invalid_param(key)
|
|
raise Discourse::InvalidParameters.new(key.to_sym)
|
|
end
|
|
|
|
def fetch_username
|
|
params.require(:user)
|
|
params[:user]
|
|
end
|
|
|
|
def fetch_email
|
|
params.require(:email)
|
|
params[:email]
|
|
end
|
|
|
|
def pm_has_slots?(pm)
|
|
guardian.is_staff? || !pm.reached_recipients_limit?
|
|
end
|
|
|
|
def bulk_unread_topic_ids
|
|
topic_query = TopicQuery.new(current_user)
|
|
|
|
if inbox = params[:private_message_inbox]
|
|
filter = private_message_filter(topic_query, inbox)
|
|
topic_query.options[:limit] = false
|
|
topics = topic_query.filter_private_messages_unread(current_user, filter)
|
|
else
|
|
topics =
|
|
TopicQuery.unread_filter(
|
|
topic_query.joined_topic_user,
|
|
whisperer: guardian.is_whisperer?,
|
|
).listable_topics
|
|
|
|
topics = TopicQuery.tracked_filter(topics, current_user.id) if params[:tracked].to_s == "true"
|
|
|
|
if params[:category_id]
|
|
category_ids =
|
|
if params[:include_subcategories]
|
|
Category.subcategory_ids(params[:category_id].to_i)
|
|
else
|
|
params[:category_id]
|
|
end
|
|
|
|
topics = topics.where(category_id: category_ids)
|
|
end
|
|
|
|
if params[:tag_name].present?
|
|
topics = topics.joins(:tags).where("tags.name": params[:tag_name])
|
|
end
|
|
end
|
|
|
|
topics.pluck(:id)
|
|
end
|
|
|
|
def private_message_filter(topic_query, inbox)
|
|
case inbox
|
|
when "group"
|
|
group_name = params[:group_name]
|
|
group = Group.find_by("lower(name) = ?", group_name)
|
|
raise Discourse::NotFound if !group
|
|
raise Discourse::NotFound if !guardian.can_see_group_messages?(group)
|
|
topic_query.options[:group_name] = group_name
|
|
:group
|
|
when "user"
|
|
:user
|
|
else
|
|
:all
|
|
end
|
|
end
|
|
end
|