require 'uri' require_dependency 'slug' class TopicLink < ActiveRecord::Base belongs_to :topic belongs_to :user belongs_to :post belongs_to :link_topic, class_name: 'Topic' validates_presence_of :url validates_length_of :url, maximum: 500 validates_uniqueness_of :url, scope: [:topic_id, :post_id] has_many :topic_link_clicks validate :link_to_self # Make sure a topic can't link to itself def link_to_self errors.add(:base, "can't link to the same topic") if (topic_id == link_topic_id) end # Extract any urls in body def self.extract_from(post) return unless post.present? TopicLink.transaction do added_urls = [] reflected_urls = [] PrettyText .extract_links(post.cooked) .map{|u| [u, URI.parse(u)] rescue nil} .reject{|u,p| p.nil?} .uniq{|u,p| u} .each do |url, parsed| begin internal = false topic_id = nil post_number = nil if parsed.host == Discourse.current_hostname || !parsed.host internal = true route = Rails.application.routes.recognize_path(parsed.path) # We aren't interested in tracking internal links to users next if route[:controller] == 'users' topic_id = route[:topic_id] post_number = route[:post_number] || 1 end # Skip linking to ourselves next if topic_id == post.topic_id added_urls << url TopicLink.create(post_id: post.id, user_id: post.user_id, topic_id: post.topic_id, url: url, domain: parsed.host || Discourse.current_hostname, internal: internal, link_topic_id: topic_id) # Create the reflection if we can if topic_id.present? topic = Topic.where(id: topic_id).first if topic && post.topic.archetype != 'private_message' && topic.archetype != 'private_message' prefix = Discourse.base_url reflected_post = nil if post_number.present? reflected_post = Post.where(topic_id: topic_id, post_number: post_number.to_i).first end reflected_url = "#{prefix}#{post.topic.relative_url(post.post_number)}" reflected_urls << reflected_url TopicLink.create(user_id: post.user_id, topic_id: topic_id, post_id: reflected_post.try(:id), url: reflected_url, domain: Discourse.current_hostname, reflection: true, internal: true, link_topic_id: post.topic_id, link_post_id: post.id) end end rescue URI::InvalidURIError # if the URI is invalid, don't store it. rescue ActionController::RoutingError # If we can't find the route, no big deal end end # Remove links that aren't there anymore if added_urls.present? TopicLink.delete_all ["(url not in (:urls)) AND (post_id = :post_id)", urls: added_urls, post_id: post.id] TopicLink.delete_all ["(url not in (:urls)) AND (link_post_id = :post_id)", urls: reflected_urls, post_id: post.id] else TopicLink.delete_all ["post_id = :post_id OR link_post_id = :post_id", post_id: post.id] end end end end