Module: DiscourseTagging
- Defined in:
- lib/discourse_tagging.rb
Constant Summary collapse
- TAGS_FIELD_NAME =
"tags"
- TAGS_FILTER_REGEXP =
/?#[]@!$&‘()*+,;=.%`^|{}“<>
/[\/\?#\[\]@!\$&'\(\)\*\+,;=\.%\\`^\s|\{\}"<>]+/
- TAGS_STAFF_CACHE_KEY =
"staff_tag_names"
- TAG_GROUP_TAG_IDS_SQL =
<<-SQL SELECT tag_id FROM tag_group_memberships tgm INNER JOIN tag_groups tg ON tgm.tag_group_id = tg.id SQL
- TAG_GROUP_RESTRICTIONS_SQL =
<<~SQL tag_group_restrictions AS ( SELECT t.id as tag_id, tgm.id as tgm_id, tg.id as tag_group_id, tg.parent_tag_id as parent_tag_id, tg.one_per_topic as one_per_topic FROM tags t LEFT OUTER JOIN tag_group_memberships tgm ON tgm.tag_id = t.id /*and_name_like*/ LEFT OUTER JOIN tag_groups tg ON tg.id = tgm.tag_group_id ) SQL
- CATEGORY_RESTRICTIONS_SQL =
<<~SQL category_restrictions AS ( SELECT t.id as tag_id, ct.id as ct_id, ct.category_id as category_id, NULL AS category_tag_group_id FROM tags t INNER JOIN category_tags ct ON t.id = ct.tag_id /*and_name_like*/ UNION SELECT t.id as tag_id, ctg.id as ctg_id, ctg.category_id as category_id, ctg.tag_group_id AS category_tag_group_id FROM tags t INNER JOIN tag_group_memberships tgm ON tgm.tag_id = t.id /*and_name_like*/ INNER JOIN category_tag_groups ctg ON tgm.tag_group_id = ctg.tag_group_id ) SQL
- PERMITTED_TAGS_SQL =
<<~SQL permitted_tag_groups AS ( SELECT tg.id as tag_group_id, tgp.group_id as group_id, tgp.permission_type as permission_type FROM tags t INNER JOIN tag_group_memberships tgm ON tgm.tag_id = t.id /*and_name_like*/ INNER JOIN tag_groups tg ON tg.id = tgm.tag_group_id INNER JOIN tag_group_permissions tgp ON tg.id = tgp.tag_group_id /*and_group_ids*/ AND tgp.permission_type = #{TagGroupPermission.[:full]} ) SQL
Class Method Summary collapse
-
.add_or_create_synonyms_by_name(target_tag, synonym_names) ⇒ Object
Returns true if all were added successfully, or an Array of the tags that failed to be added, with errors on each Tag.
- .add_or_create_tags_by_name(taggable, tag_names_arg, opts = {}) ⇒ Object
- .clean_tag(tag) ⇒ Object
- .clear_cache! ⇒ Object
-
.filter_allowed_tags(guardian, opts = {}) ⇒ Object
Options: term: a search term to filter tags by name term_type: whether to search by “starts_with” or “contains” with the term limit: max number of results category: a Category to which the object being tagged belongs for_input: result is for an input field, so only show permitted tags for_topic: results are for tagging a topic selected_tags: an array of tag names that are in the current selection only_tag_names: limit results to tags with these names exclude_synonyms: exclude synonyms from results order_search_results: result should be ordered for name search results order_popularity: order result by topic_count excluded_tag_names: an array of tag names not to include in the results.
- .filter_tags_violating_one_tag_from_group_per_topic(guardian, category, tags = []) ⇒ Object
- .filter_visible(query, guardian = nil) ⇒ Object
- .hidden_tag_names(guardian = nil) ⇒ Object
- .muted_tags(user) ⇒ Object
- .permitted_group_ids(guardian = nil) ⇒ Object
- .permitted_group_ids_query(guardian = nil) ⇒ Object
-
.permitted_tag_names(guardian = nil) ⇒ Object
explicit permissions to use these tags.
-
.readonly_tag_names(guardian = nil) ⇒ Object
read-only tags for this user.
-
.staff_tag_names ⇒ Object
middle level of tag group restrictions.
- .tag_topic_by_names(topic, guardian, tag_names_arg, append: false) ⇒ Object
- .tags_for_saving(tags_arg, guardian, opts = {}) ⇒ Object
- .term_types ⇒ Object
- .validate_category_restricted_tags(guardian, model, category, tags = []) ⇒ Object
- .validate_category_tags(guardian, model, category, tags = []) ⇒ Object
- .validate_min_required_tags_for_category(guardian, model, category, tags = []) ⇒ Object
- .validate_one_tag_from_group_per_topic(guardian, model, category, tags = []) ⇒ Object
- .validate_required_tags_from_group(guardian, model, category, tags = []) ⇒ Object
- .visible_tags(guardian) ⇒ Object
Class Method Details
.add_or_create_synonyms_by_name(target_tag, synonym_names) ⇒ Object
Returns true if all were added successfully, or an Array of the tags that failed to be added, with errors on each Tag.
765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 |
# File 'lib/discourse_tagging.rb', line 765 def self.add_or_create_synonyms_by_name(target_tag, synonym_names) tag_names = DiscourseTagging.(synonym_names, Guardian.new(Discourse.system_user)) || [] tag_names -= [target_tag.name] existing = Tag.where_name(tag_names).all target_tag.synonyms << existing (tag_names - target_tag.synonyms.map(&:name)).each do |name| target_tag.synonyms << Tag.create(name: name) end successful = existing.select { |t| !t.errors.present? } synonyms_ids = successful.map(&:id) TopicTag.where(topic_id: target_tag.topics.with_deleted, tag_id: synonyms_ids).delete_all TopicTag.joins(DB.sql_fragment(<<~SQL, synonyms_ids: synonyms_ids)).delete_all INNER JOIN ( SELECT MIN(id) AS id, topic_id FROM topic_tags WHERE tag_id IN (:synonyms_ids) GROUP BY topic_id ) AS tt ON tt.id < topic_tags.id AND tt.topic_id = topic_tags.topic_id AND topic_tags.tag_id IN (:synonyms_ids) SQL TopicTag.where(tag_id: synonyms_ids).update_all(tag_id: target_tag.id) Scheduler::Defer.later "Update tag topic counts" do Tag.ensure_consistency! end (existing - successful).presence || true end |
.add_or_create_tags_by_name(taggable, tag_names_arg, opts = {}) ⇒ Object
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 |
# File 'lib/discourse_tagging.rb', line 747 def self.(taggable, tag_names_arg, opts = {}) tag_names = DiscourseTagging.(tag_names_arg, Guardian.new(Discourse.system_user), opts) || [] if taggable..pluck(:name).sort != tag_names.sort taggable. = Tag.where_name(tag_names).all new_tag_names = taggable..size < tag_names.size ? tag_names - taggable..map(&:name) : [] taggable. << Tag .where(target_tag_id: taggable..map(&:id)) .where.not(id: taggable..map(&:id)) .all new_tag_names.each { |name| taggable. << Tag.create(name: name) } end end |
.clean_tag(tag) ⇒ Object
722 723 724 725 726 727 728 729 730 731 |
# File 'lib/discourse_tagging.rb', line 722 def self.clean_tag(tag) tag = tag.dup tag.downcase! if SiteSetting. tag.strip! tag.gsub!(/[[:space:]]+/, "-") tag.gsub!(/[^[:word:][:punct:]]+/, "") tag.gsub!(TAGS_FILTER_REGEXP, "") tag.squeeze!("-") tag[0...SiteSetting.max_tag_length] end |
.clear_cache! ⇒ Object
718 719 720 |
# File 'lib/discourse_tagging.rb', line 718 def self.clear_cache! Discourse.cache.delete(TAGS_STAFF_CACHE_KEY) end |
.filter_allowed_tags(guardian, opts = {}) ⇒ Object
Options:
term: a search term to filter tags by name
term_type: whether to search by "starts_with" or "contains" with the term
limit: max number of results
category: a Category to which the object being tagged belongs
for_input: result is for an input field, so only show permitted tags
for_topic: results are for tagging a topic
selected_tags: an array of tag names that are in the current selection
only_tag_names: limit results to tags with these names
exclude_synonyms: exclude synonyms from results
order_search_results: result should be ordered for name search results
order_popularity: order result by topic_count
excluded_tag_names: an array of tag names not to include in the results
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 |
# File 'lib/discourse_tagging.rb', line 423 def self.(guardian, opts = {}) selected_tag_ids = opts[:selected_tags] ? Tag.where_name(opts[:selected_tags]).pluck(:id) : [] category = opts[:category] = category ? (category..count > 0 || category.tag_groups.count > 0) : false # If guardian is nil, it means the caller doesn't want tags to be filtered # based on guardian rules. Use the same rules as for staff users. filter_for_non_staff = !guardian.nil? && !guardian.is_staff? builder_params = {} builder_params[:selected_tag_ids] = selected_tag_ids unless selected_tag_ids.empty? sql = +"WITH #{TAG_GROUP_RESTRICTIONS_SQL}, #{CATEGORY_RESTRICTIONS_SQL}" if (opts[:for_input] || opts[:for_topic]) && filter_for_non_staff sql << ", #{PERMITTED_TAGS_SQL} " builder_params[:group_ids] = permitted_group_ids(guardian) sql.gsub!("/*and_group_ids*/", "AND group_id IN (:group_ids)") end outer_join = category.nil? || category. || ! topic_count_column = Tag.topic_count_column(guardian) distinct_clause = if opts[:order_popularity] "DISTINCT ON (#{topic_count_column}, name)" elsif opts[:order_search_results] && opts[:term].present? "DISTINCT ON (lower(name) = lower(:cleaned_term), #{topic_count_column}, name)" else "" end sql << <<~SQL SELECT #{distinct_clause} t.id, t.name, t.#{topic_count_column}, t.pm_topic_count, t.description, tgr.tgm_id as tgm_id, tgr.tag_group_id as tag_group_id, tgr.parent_tag_id as parent_tag_id, tgr.one_per_topic as one_per_topic, t.target_tag_id FROM tags t INNER JOIN tag_group_restrictions tgr ON tgr.tag_id = t.id #{outer_join ? "LEFT OUTER" : "INNER"} JOIN category_restrictions cr ON t.id = cr.tag_id AND (tgr.tag_group_id = cr.category_tag_group_id OR cr.category_tag_group_id IS NULL) /*where*/ /*order_by*/ /*limit*/ SQL builder = DB.build(sql) if !opts[:for_topic] && builder_params[:selected_tag_ids] builder.where("id NOT IN (:selected_tag_ids)") end if opts[:only_tag_names] builder.where("LOWER(name) IN (:only_tag_names)") builder_params[:only_tag_names] = opts[:only_tag_names].map(&:downcase) end # parent tag requirements if opts[:for_input] builder.where( ( if builder_params[:selected_tag_ids] "tgm_id IS NULL OR parent_tag_id IS NULL OR parent_tag_id IN (:selected_tag_ids)" else "tgm_id IS NULL OR parent_tag_id IS NULL" end ), ) end if category && builder.where( category. ? "category_id = ? OR category_id IS NULL" : "category_id = ?", category.id, ) elsif category || opts[:for_input] || opts[:for_topic] # tags not restricted to any categories builder.where("category_id IS NULL") end if filter_for_non_staff && (opts[:for_input] || opts[:for_topic]) # exclude staff-only tag groups builder.where( "tag_group_id IS NULL OR tag_group_id IN (SELECT tag_group_id FROM permitted_tag_groups)", ) end term = opts[:term] if term.present? builder_params[:cleaned_term] = term if opts[:term_type] == DiscourseTagging.term_types[:starts_with] builder.where("starts_with(LOWER(name), LOWER(:cleaned_term))") sql.gsub!("/*and_name_like*/", "AND starts_with(LOWER(t.name), LOWER(:cleaned_term))") else builder.where("position(LOWER(:cleaned_term) IN LOWER(t.name)) <> 0") sql.gsub!("/*and_name_like*/", "AND position(LOWER(:cleaned_term) IN LOWER(t.name)) <> 0") end else sql.gsub!("/*and_name_like*/", "") end # show required tags for non-staff # or for staff when # - there are more available tags than the query limit # - and no search term has been included required_tag_ids = nil required_category_tag_group = nil if opts[:for_input] && category&.category_required_tag_groups.present? && (filter_for_non_staff || term.blank?) category.category_required_tag_groups.each do |crtg| = crtg.tag_group..pluck(:id) next if ( & selected_tag_ids).size >= crtg.min_count if filter_for_non_staff || .size >= opts[:limit].to_i required_category_tag_group = crtg required_tag_ids = builder.where("id IN (?)", required_tag_ids) end break end end if filter_for_non_staff group_ids = permitted_group_ids(guardian) builder.where(<<~SQL, group_ids, group_ids) id NOT IN ( (SELECT tgm.tag_id FROM tag_group_permissions tgp INNER JOIN tag_groups tg ON tgp.tag_group_id = tg.id INNER JOIN tag_group_memberships tgm ON tg.id = tgm.tag_group_id WHERE tgp.group_id NOT IN (?)) EXCEPT (SELECT tgm.tag_id FROM tag_group_permissions tgp INNER JOIN tag_groups tg ON tgp.tag_group_id = tg.id INNER JOIN tag_group_memberships tgm ON tg.id = tgm.tag_group_id WHERE tgp.group_id IN (?)) ) SQL end if builder_params[:selected_tag_ids] && (opts[:for_input] || opts[:for_topic]) one_tag_per_group_ids = DB.query(<<~SQL, builder_params[:selected_tag_ids]).map(&:id) SELECT DISTINCT(tg.id) FROM tag_groups tg INNER JOIN tag_group_memberships tgm ON tg.id = tgm.tag_group_id AND tgm.tag_id IN (?) WHERE tg.one_per_topic SQL if one_tag_per_group_ids.present? builder.where( "t.id NOT IN (SELECT DISTINCT tag_id FROM tag_group_restrictions WHERE tag_group_id IN (?)) OR id IN (:selected_tag_ids)", one_tag_per_group_ids, ) end end builder.where("target_tag_id IS NULL") if opts[:exclude_synonyms] if opts[:exclude_has_synonyms] builder.where("id NOT IN (SELECT target_tag_id FROM tags WHERE target_tag_id IS NOT NULL)") end builder.where("name NOT IN (?)", opts[:excluded_tag_names]) if opts[:excluded_tag_names]&.any? if opts[:limit] if required_tag_ids && term.blank? # override limit so all required tags are shown by default builder.limit(required_tag_ids.size) else builder.limit(opts[:limit]) end end if opts[:order_popularity] builder.order_by("#{topic_count_column} DESC, name") elsif opts[:order_search_results] && !term.blank? builder.order_by("lower(name) = lower(:cleaned_term) DESC, #{topic_count_column} DESC, name") end result = builder.query(builder_params).uniq { |t| t.id } if opts[:with_context] context = {} if required_category_tag_group context[:required_tag_group] = { name: required_category_tag_group.tag_group.name, min_count: required_category_tag_group.min_count, } end [result, context] else result end end |
.filter_tags_violating_one_tag_from_group_per_topic(guardian, category, tags = []) ⇒ Object
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 |
# File 'lib/discourse_tagging.rb', line 343 def self.(guardian, category, = []) return [] if .size < 2 # ensures that tags are a list of tag names = .map(&:name) if Tag === [0] = ( guardian, category: category, only_tag_names: , for_topic: true, order_search_results: true, ) return {} if .size < 2 = .sort_by { |tag| [tag.tag_group_id || -1, tag.name] } .inject({}) do |hash, tag| next hash unless tag.one_per_topic hash[tag.tag_group_id] = (hash[tag.tag_group_id] || []) << tag hash end .select { |_, | .size > 1 } end |
.filter_visible(query, guardian = nil) ⇒ Object
642 643 644 |
# File 'lib/discourse_tagging.rb', line 642 def self.filter_visible(query, guardian = nil) guardian&.is_staff? ? query : query.where(id: (guardian).select(:id)) end |
.hidden_tag_names(guardian = nil) ⇒ Object
646 647 648 |
# File 'lib/discourse_tagging.rb', line 646 def self.hidden_tag_names(guardian = nil) guardian&.is_staff? ? [] : Tag.where.not(id: (guardian).select(:id)).pluck(:name) end |
.muted_tags(user) ⇒ Object
794 795 796 797 |
# File 'lib/discourse_tagging.rb', line 794 def self.(user) return [] unless user TagUser.lookup(user, :muted).joins(:tag).pluck("tags.name") end |
.permitted_group_ids(guardian = nil) ⇒ Object
667 668 669 |
# File 'lib/discourse_tagging.rb', line 667 def self.permitted_group_ids(guardian = nil) permitted_group_ids_query(guardian).pluck(:id) end |
.permitted_group_ids_query(guardian = nil) ⇒ Object
650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 |
# File 'lib/discourse_tagging.rb', line 650 def self.permitted_group_ids_query(guardian = nil) if guardian&.authenticated? Group.from( Group.sanitize_sql( [ "(SELECT ? AS id UNION #{guardian.user.groups.select(:id).to_sql}) as groups", Group::AUTO_GROUPS[:everyone], ], ), ).select(:id) else Group.from( Group.sanitize_sql(["(SELECT ? AS id) AS groups", Group::AUTO_GROUPS[:everyone]]), ).select(:id) end end |
.permitted_tag_names(guardian = nil) ⇒ Object
explicit permissions to use these tags
685 686 687 688 689 690 691 692 693 694 695 |
# File 'lib/discourse_tagging.rb', line 685 def self.permitted_tag_names(guardian = nil) query = Tag.joins(tag_groups: :tag_group_permissions).where( tag_group_permissions: { group_id: permitted_group_ids(guardian), permission_type: TagGroupPermission.[:full], }, ) query.pluck(:name).uniq end |
.readonly_tag_names(guardian = nil) ⇒ Object
read-only tags for this user
672 673 674 675 676 677 678 679 680 681 682 |
# File 'lib/discourse_tagging.rb', line 672 def self.readonly_tag_names(guardian = nil) return [] if guardian&.is_staff? query = Tag.joins(tag_groups: :tag_group_permissions).where( "tag_group_permissions.permission_type = ?", TagGroupPermission.[:readonly], ) query.pluck(:name) end |
.staff_tag_names ⇒ Object
middle level of tag group restrictions
698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 |
# File 'lib/discourse_tagging.rb', line 698 def self.staff_tag_names tag_names = Discourse.cache.read(TAGS_STAFF_CACHE_KEY) if !tag_names tag_names = Tag .joins(tag_groups: :tag_group_permissions) .where( tag_group_permissions: { group_id: Group::AUTO_GROUPS[:everyone], permission_type: TagGroupPermission.[:readonly], }, ) .pluck(:name) Discourse.cache.write(TAGS_STAFF_CACHE_KEY, tag_names, expires_in: 1.hour) end tag_names end |
.tag_topic_by_names(topic, guardian, tag_names_arg, append: false) ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/discourse_tagging.rb', line 19 def self.tag_topic_by_names(topic, guardian, tag_names_arg, append: false) if guardian.can_tag?(topic) tag_names = DiscourseTagging.(tag_names_arg, guardian) || [] if !tag_names.empty? Tag .where_name(tag_names) .joins(:target_tag) .includes(:target_tag) .each { |tag| tag_names[tag_names.index(tag.name)] = tag.target_tag.name } end # tags currently on the topic old_tag_names = topic..pluck(:name) || [] # tags we're trying to add to the topic new_tag_names = tag_names - old_tag_names # tag names being removed from the topic removed_tag_names = old_tag_names - tag_names # tag names which are visible, but not usable, by *some users* = DiscourseTagging.readonly_tag_names(guardian) # tags names which are not visible or usable by this user = DiscourseTagging.hidden_tag_names(guardian) # tag names which ARE permitted by *this user* = DiscourseTagging.permitted_tag_names(guardian) # If this user has explicit permission to use certain tags, # we need to ensure those tags are removed from the list of # restricted tags = - if .present? # visible, but not usable, tags this user is trying to use = new_tag_names & # hidden tags this user is trying to use += new_tag_names & if .present? topic.errors.add( :base, I18n.t("tags.restricted_tag_disallowed", tag: .join(" ")), ) return false end = removed_tag_names & if .present? topic.errors.add( :base, I18n.t("tags.restricted_tag_remove_disallowed", tag: .join(" ")), ) return false end tag_names += removed_tag_names & category = topic.category tag_names = tag_names + old_tag_names if append if tag_names.present? # guardian is explicitly nil cause we don't want to strip all # staff tags that already passed validation = ( nil, # guardian for_topic: true, category: category, selected_tags: tag_names, only_tag_names: tag_names, ) # keep existent tags that current user cannot use += Tag.where(name: old_tag_names & tag_names) = Tag.where(id: .map(&:id)).all.to_a if .size > 0 if .size < tag_names.size && ( category.nil? || category. || (category..count == 0 && category.tag_groups.count == 0) ) tag_names.each do |name| << Tag.create(name: name) unless Tag.where_name(name).exists? end end # tests if there are conflicts between tags on tag groups that only allow one tag from the group before adding # mandatory parent tags because later we want to test if the mandatory parent tags introduce any conflicts # and be able to pinpoint the tag that is introducing it # guardian like above is nil to prevent stripping tags that already passed validation return false unless validate_one_tag_from_group_per_topic(nil, topic, category, ) # add missing mandatory parent tags tag_ids = .map(&:id) = DB .query( " SELECT tgm.tag_id, tg.parent_tag_id FROM tag_groups tg INNER JOIN tag_group_memberships tgm ON tgm.tag_group_id = tg.id WHERE tg.parent_tag_id IS NOT NULL AND tgm.tag_id IN (?) ", tag_ids, ) .inject({}) do |h, v| h[v.tag_id] ||= [] h[v.tag_id] << v.parent_tag_id h end missing_parent_tag_ids = .map do |_, parent_tag_ids| (tag_ids & parent_tag_ids).size == 0 ? parent_tag_ids.first : nil end .compact .uniq = Tag.where(id: missing_parent_tag_ids).all = + unless .empty? parent_tag_conflicts = ( nil, # guardian like above is nil to prevent stripping tags that already passed validation topic.category, , ) if parent_tag_conflicts.present? # we need to get the original tag names that introduced conflicting missing parent tags to return an useful # error message parent_child_names_map = {} .each do |tag_id, parent_tag_ids| next if (tag_ids & parent_tag_ids).size > 0 # tag already has a parent tag parent_tag = .select { |t| t.id == parent_tag_ids.first }.first original_child_tag = .select { |t| t.id == tag_id }.first next if parent_tag.blank? || original_child_tag.blank? parent_child_names_map[parent_tag.name] = original_child_tag.name end # replaces the added missing parent tags with the original tag parent_tag_conflicts.map do |_, | topic.errors.add( :base, I18n.t( "tags.limited_to_one_tag_from_group", tags: .map do |tag| tag_name = tag.name if parent_child_names_map[tag_name].present? parent_child_names_map[tag_name] else tag_name end end .uniq .sort .join(", "), ), ) end return false end return false unless (guardian, topic, category, ) return false unless (guardian, topic, category, ) if .size == 0 topic.errors.add(:base, I18n.t("tags.forbidden.invalid", count: new_tag_names.size)) return false end topic. = else return false unless (guardian, topic, category) return false unless (guardian, topic, category) topic. = [] end topic. = true DiscourseEvent.trigger( :topic_tags_changed, topic, old_tag_names: old_tag_names, new_tag_names: topic..map(&:name), user: guardian.user, ) true else topic.errors.add(:base, I18n.t("tags.user_not_permitted")) false end end |
.tags_for_saving(tags_arg, guardian, opts = {}) ⇒ Object
733 734 735 736 737 738 739 740 741 742 743 744 745 |
# File 'lib/discourse_tagging.rb', line 733 def self.(, guardian, opts = {}) return [] unless guardian.can_tag_topics? && .present? tag_names = Tag.where_name().pluck(:name) if guardian.can_create_tag? tag_names += ( - tag_names).map { |t| clean_tag(t) } tag_names.delete_if { |t| t.blank? } tag_names.uniq! end opts[:unlimited] ? tag_names : tag_names[0...SiteSetting.] end |
.term_types ⇒ Object
15 16 17 |
# File 'lib/discourse_tagging.rb', line 15 def self.term_types @term_types ||= Enum.new(contains: 0, starts_with: 1) end |
.validate_category_restricted_tags(guardian, model, category, tags = []) ⇒ Object
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/discourse_tagging.rb', line 275 def self.(guardian, model, category, = []) return true if .blank? || category.blank? = .map(&:name) if Tag === [0] = Hash.new { |h, k| h[k] = Set.new } query = Tag.where(name: ) query .joins(tag_groups: :categories) .pluck(:name, "categories.id") .each { |(tag, cat_id)| [tag] << cat_id } query .joins(:categories) .pluck(:name, "categories.id") .each { |(tag, cat_id)| [tag] << cat_id } = .keys.select do |tag| ![tag].include?(category.id) end if .present? msg = I18n.t( "tags.forbidden.restricted_tags_cannot_be_used_in_category", count: .size, tags: .sort.join(", "), category: category.name, ) model.errors.add(:base, msg) return false end if !category. && category. = - .keys if .present? msg = I18n.t( "tags.forbidden.category_does_not_allow_tags", count: .size, tags: .sort.join(", "), category: category.name, ) model.errors.add(:base, msg) return false end end true end |
.validate_category_tags(guardian, model, category, tags = []) ⇒ Object
225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/discourse_tagging.rb', line 225 def self.(guardian, model, category, = []) = .present? ? Tag.where(name: ) : [] = guardian.can_create_tag? ? : # all add to model (topic) errors valid = (guardian, model, category, ) valid &&= (guardian, model, category, ) valid &&= (guardian, model, category, ) valid &&= validate_one_tag_from_group_per_topic(guardian, model, category, ) valid end |
.validate_min_required_tags_for_category(guardian, model, category, tags = []) ⇒ Object
238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/discourse_tagging.rb', line 238 def self.(guardian, model, category, = []) if !guardian.is_staff? && category && category. > 0 && .length < category. model.errors.add( :base, I18n.t("tags.minimum_required_tags", count: category.), ) false else true end end |
.validate_one_tag_from_group_per_topic(guardian, model, category, tags = []) ⇒ Object
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 |
# File 'lib/discourse_tagging.rb', line 325 def self.validate_one_tag_from_group_per_topic(guardian, model, category, = []) = (guardian, category, ) return true if .blank? .each do |_, | model.errors.add( :base, I18n.t( "tags.limited_to_one_tag_from_group", tags: .map(&:name).sort.join(", "), ), ) end false end |
.validate_required_tags_from_group(guardian, model, category, tags = []) ⇒ Object
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/discourse_tagging.rb', line 251 def self.(guardian, model, category, = []) return true if guardian.is_staff? || category.nil? success = true category.category_required_tag_groups.each do |crtg| if .length < crtg.min_count || crtg.tag_group..where("tags.id in (?)", .map(&:id)).count < crtg.min_count success = false model.errors.add( :base, I18n.t( "tags.required_tags_from_group", count: crtg.min_count, tag_group_name: crtg.tag_group.name, tags: crtg.tag_group..order(:id).pluck(:name).join(", "), ), ) end end success end |
.visible_tags(guardian) ⇒ Object
623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 |
# File 'lib/discourse_tagging.rb', line 623 def self.(guardian) if guardian&.is_staff? Tag.all else # Visible tags either have no permissions or have allowable permissions Tag .where.not(id: TagGroupMembership.joins(tag_group: :tag_group_permissions).select(:tag_id)) .or( Tag.where( id: TagGroupPermission .joins(tag_group: :tag_group_memberships) .where(group_id: permitted_group_ids_query(guardian)) .select("tag_group_memberships.tag_id"), ), ) end end |