summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2012-10-29 00:35:19 -0300
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2012-10-29 00:35:19 -0300
commitc83d17e957ae1b725e9d2ea24e6eeba0e341409f (patch)
tree149810cf1941e77e1ab6abb0962eab0a44ff3dec
parent6e4f4482853fd20f8f8301e96174da77c528e415 (diff)
downloadaskbot-c83d17e957ae1b725e9d2ea24e6eeba0e341409f.tar.gz
askbot-c83d17e957ae1b725e9d2ea24e6eeba0e341409f.tar.bz2
askbot-c83d17e957ae1b725e9d2ea24e6eeba0e341409f.zip
reimplemented the link insertion rep barrier
-rw-r--r--askbot/conf/minimum_reputation.py9
-rw-r--r--askbot/doc/source/changelog.rst1
-rw-r--r--askbot/models/__init__.py21
-rw-r--r--askbot/models/post.py2
-rw-r--r--askbot/tests/email_alert_tests.py6
-rw-r--r--askbot/tests/utils_tests.py52
-rw-r--r--askbot/utils/html.py39
7 files changed, 124 insertions, 6 deletions
diff --git a/askbot/conf/minimum_reputation.py b/askbot/conf/minimum_reputation.py
index 152a2079..359855c9 100644
--- a/askbot/conf/minimum_reputation.py
+++ b/askbot/conf/minimum_reputation.py
@@ -107,6 +107,15 @@ settings.register(
settings.register(
livesettings.IntegerValue(
MIN_REP,
+ 'MIN_REP_TO_INSERT_LINK',
+ default=10,
+ description=_('Insert links')
+ )
+)
+
+settings.register(
+ livesettings.IntegerValue(
+ MIN_REP,
'MIN_REP_TO_CLOSE_OWN_QUESTIONS',
default=25,
description=_('Close own questions'),
diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst
index a971d9d6..fcc85c21 100644
--- a/askbot/doc/source/changelog.rst
+++ b/askbot/doc/source/changelog.rst
@@ -3,6 +3,7 @@ Changes in Askbot
Development version
-------------------
+* Added minimum reputation to insert links and hotlinked images (Evgeny)
* Added support of Haystack for search (Adolfo)
* Added minimum reputation setting to accept any answer as correct (Evgeny)
* Added "VIP" option to groups - if checked, all posts belong to the group and users of that group in the future will be able to moderate those posts. Moderation features for VIP group are in progress (Evgeny)
diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py
index c2b6e5c1..ed687f86 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -57,6 +57,7 @@ from askbot.models.widgets import AskWidget, QuestionWidget
from askbot import auth
from askbot.utils.decorators import auto_now_timestamp
from askbot.utils.slug import slugify
+from askbot.utils.html import replace_links_with_text
from askbot.utils.html import sanitize_html
from askbot.utils.diff import textDiff as htmldiff
from askbot.utils.url_utils import strip_path
@@ -2478,6 +2479,25 @@ def _process_vote(user, post, timestamp=None, cancel=False, vote_type=None):
)
return vote
+def user_fix_html_links(self, text):
+ """depending on the user's privilege, allow links
+ and hotlinked images or replace them with plain text
+ url
+ """
+ is_simple_user = not self.is_administrator_or_moderator()
+ has_low_rep = self.reputation < askbot_settings.MIN_REP_TO_INSERT_LINK
+ if is_simple_user and has_low_rep:
+ result = replace_links_with_text(text)
+ if result != text:
+ message = ungettext(
+ 'At least %d karma point is required to insert links',
+ 'At least %d karma points is required to insert links',
+ askbot_settings.MIN_REP_TO_INSERT_LINK
+ ) % askbot_settings.MIN_REP_TO_INSERT_LINK
+ self.message_set.create(message=message)
+ return result
+ return text
+
def user_unfollow_question(self, question = None):
self.followed_threads.remove(question.thread)
@@ -2790,6 +2810,7 @@ User.add_to_class('get_tag_filtered_questions', user_get_tag_filtered_questions)
User.add_to_class('get_messages', get_messages)
User.add_to_class('delete_messages', delete_messages)
User.add_to_class('toggle_favorite_question', toggle_favorite_question)
+User.add_to_class('fix_html_links', user_fix_html_links)
User.add_to_class('follow_question', user_follow_question)
User.add_to_class('unfollow_question', user_unfollow_question)
User.add_to_class('is_following_question', user_is_following_question)
diff --git a/askbot/models/post.py b/askbot/models/post.py
index daf9c93f..4e841f13 100644
--- a/askbot/models/post.py
+++ b/askbot/models/post.py
@@ -503,8 +503,8 @@ class Post(models.Model):
last_revision = self.html
data = self.parse_post_text()
+ self.html = author.fix_html_links(data['html'])
- self.html = data['html']
newly_mentioned_users = set(data['newly_mentioned_users']) - set([author])
removed_mentions = data['removed_mentions']
diff --git a/askbot/tests/email_alert_tests.py b/askbot/tests/email_alert_tests.py
index f4ae052b..bdb361e5 100644
--- a/askbot/tests/email_alert_tests.py
+++ b/askbot/tests/email_alert_tests.py
@@ -1037,7 +1037,11 @@ class PostApprovalTests(utils.AskbotTestCase):
class AbsolutizeUrlsInEmailsTests(utils.AskbotTestCase):
- @with_settings(MIN_REP_TO_TRIGGER_EMAIL=1, APP_URL='http://example.com/')
+ @with_settings(
+ MIN_REP_TO_TRIGGER_EMAIL=1,
+ APP_URL='http://example.com/',
+ MIN_REP_TO_INSERT_LINK=1
+ )
def test_urls_are_absolute(self):
u1 = self.create_user('u1')
max_email = models.EmailFeedSetting.MAX_EMAIL_SCHEDULE
diff --git a/askbot/tests/utils_tests.py b/askbot/tests/utils_tests.py
index c8526ad1..ed845f48 100644
--- a/askbot/tests/utils_tests.py
+++ b/askbot/tests/utils_tests.py
@@ -2,6 +2,7 @@ from django.test import TestCase
from askbot.tests.utils import with_settings
from askbot.utils.url_utils import urls_equal
from askbot.utils.html import absolutize_urls
+from askbot.utils.html import replace_links_with_text
from askbot.conf import settings as askbot_settings
class UrlUtilsTests(TestCase):
@@ -17,11 +18,58 @@ class UrlUtilsTests(TestCase):
self.assertTrue(e('http://cnn.com/path', 'http://cnn.com/path/', True))
self.assertFalse(e('http://cnn.com/path', 'http://cnn.com/path/'))
-
+
+
+class ReplaceLinksWithTextTests(TestCase):
+ """testing correctness of `askbot.utils.html.replace_links_with_text"""
+
+ def test_local_link_not_replaced(self):
+ text = '<a href="/some-link">some link</a>'
+ self.assertEqual(replace_links_with_text(text), text)
+
+ def test_link_without_url_replaced(self):
+ text = '<a>some link</a>'
+ self.assertEqual(replace_links_with_text(text), 'some link')
+
+ def test_external_link_without_text_replaced(self):
+ text = '<a href="https://example.com/"></a>'
+ #in this case we delete the link
+ self.assertEqual(replace_links_with_text(text), '')
+
+ def test_external_link_with_text_replaced(self):
+ text = '<a href="https://example.com/">some link</a>'
+ self.assertEqual(
+ replace_links_with_text(text),
+ 'https://example.com/ (some link)'
+ )
+
+ def test_local_image_not_replaced(self):
+ text = '<img src="/some-image.gif"/>'
+ self.assertEqual(replace_links_with_text(text), text)
+
+ def test_local_url_with_hotlinked_image_replaced(self):
+ text = '<a href="/some-link"><img src="http://example.com/img.png" alt="picture""> some text</a>'
+ self.assertEqual(
+ replace_links_with_text(text),
+ '<a href="/some-link">http://example.com/img.png (picture) some text</a>'
+ )
+
+ def test_hotlinked_image_without_alt_replaced(self):
+ text = '<img src="https://example.com/some-image.gif"/>'
+ self.assertEqual(
+ replace_links_with_text(text),
+ 'https://example.com/some-image.gif'
+ )
+
+ def test_hotlinked_image_with_alt_replaced(self):
+ text = '<img src="https://example.com/some-image.gif" alt="picture"/>'
+ self.assertEqual(
+ replace_links_with_text(text),
+ 'https://example.com/some-image.gif (picture)'
+ )
class HTMLUtilsTests(TestCase):
"""tests for :mod:`askbot.utils.html` module"""
-
@with_settings(APP_URL='http://example.com')
def test_absolutize_urls(self):
text = """<img class="junk" src="/some.gif"> <img class="junk" src="/cat.gif"> <IMG SRC='/some.png'>"""
diff --git a/askbot/utils/html.py b/askbot/utils/html.py
index 49eddee2..3e1bfba5 100644
--- a/askbot/utils/html.py
+++ b/askbot/utils/html.py
@@ -1,11 +1,11 @@
"""Utilities for working with HTML."""
+from bs4 import BeautifulSoup
import html5lib
from html5lib import sanitizer, serializer, tokenizer, treebuilders, treewalkers
import re
import htmlentitydefs
from urlparse import urlparse
from django.core.urlresolvers import reverse
-from django.utils.html import escape
from askbot.conf import settings as askbot_settings
class HTMLSanitizerMixin(sanitizer.HTMLSanitizerMixin):
@@ -55,12 +55,47 @@ def absolutize_urls(html):
img_replacement = '\g<prefix>"%s/\g<url>" style="max-width:500px;"' % askbot_settings.APP_URL
replacement = '\g<prefix>"%s\g<url>"' % askbot_settings.APP_URL
html = url_re1.sub(img_replacement, html)
- html= url_re2.sub(img_replacement, html)
+ html = url_re2.sub(img_replacement, html)
html = url_re3.sub(replacement, html)
#temporal fix for bad regex with wysiwyg editor
return url_re4.sub(replacement, html).replace('%s//' % askbot_settings.APP_URL,
'%s/' % askbot_settings.APP_URL)
+def replace_links_with_text(html):
+ """any absolute links will be replaced with the
+ url in plain text, same with any img tags
+ """
+ def format_url_replacement(url, text):
+ url = url.strip()
+ text = text.strip()
+ url_domain = urlparse(url).netloc
+ if url and text and url_domain != text and url != text:
+ return '%s (%s)' % (url, text)
+ return url or text or ''
+
+ soup = BeautifulSoup(html)
+ abs_url_re = r'^http(s)?://'
+
+ images = soup.find_all('img')
+ for image in images:
+ url = image.get('src', '')
+ text = image.get('alt', '')
+ if url == '' or re.match(abs_url_re, url):
+ image.replaceWith(format_url_replacement(url, text))
+
+ links = soup.find_all('a')
+ for link in links:
+ url = link.get('href', '')
+ text = ''.join(link.text) or ''
+
+ if text == '':#this is due to an issue with url inlining in comments
+ link.replaceWith('')
+ elif url == '' or re.match(abs_url_re, url):
+ link.replaceWith(format_url_replacement(url, text))
+
+ return soup.find('body').renderContents()
+
+
def sanitize_html(html):
"""Sanitizes an HTML fragment."""
p = html5lib.HTMLParser(tokenizer=HTMLSanitizer,