2012-09-24 14:25:41 +03:00
# coding: utf-8
2009-01-17 14:42:12 +02:00
import re
2012-09-24 14:25:41 +03:00
from HTMLParser import HTMLParser , HTMLParseError
2012-10-22 11:18:19 -04:00
import postmarkup
2012-12-05 21:24:42 -05:00
from postmarkup . parser import strip_bbcode
from urlparse import urlparse
2010-11-28 17:57:54 +02:00
try :
import markdown
except ImportError :
pass
2009-01-05 14:30:08 +02:00
2012-09-24 14:25:41 +03:00
from django . conf import settings
2009-01-05 14:30:08 +02:00
from django . shortcuts import render_to_response
from django . template import RequestContext
2009-08-24 23:37:53 +03:00
from django . http import HttpResponse , Http404
2013-01-18 19:19:17 -05:00
from django . core . urlresolvers import reverse
2009-01-05 14:30:08 +02:00
from django . utils . functional import Promise
2009-03-03 18:30:41 +02:00
from django . utils . translation import force_unicode , check_for_language
2009-01-05 14:30:08 +02:00
from django . utils . simplejson import JSONEncoder
2012-12-05 21:24:42 -05:00
from django . utils . translation import ugettext_lazy as _
2013-01-30 19:15:52 -05:00
from django . utils import timezone
2009-08-24 23:37:53 +03:00
from django . core . paginator import Paginator , EmptyPage , InvalidPage
2010-02-17 14:03:30 +02:00
from django . contrib . sites . models import Site
2009-01-05 14:30:08 +02:00
2009-12-23 17:06:48 +02:00
from djangobb_forum import settings as forum_settings
2009-03-03 18:30:41 +02:00
2014-02-13 14:26:57 -05:00
import logging
logger = logging . getLogger ( __name__ )
2011-08-11 17:15:08 +03:00
2009-01-21 18:28:36 +02:00
#compile smiles regexp
_SMILES = [ ( re . compile ( smile_re ) , path ) for smile_re , path in forum_settings . SMILES ]
2009-01-19 19:50:01 +02:00
def absolute_url ( path ) :
2010-02-17 14:03:30 +02:00
return ' http:// %s %s ' % ( Site . objects . get_current ( ) . domain , path )
2009-01-05 14:30:08 +02:00
2010-01-04 14:17:36 +02:00
2013-01-17 22:09:37 -05:00
def can_close_topic ( user , topic ) :
2013-01-30 19:15:52 -05:00
return user == topic . user and user . has_perm ( ' djangobb_forum.delayed_close ' ) and ( timezone . now ( ) - topic . created ) . total_seconds ( ) > = forum_settings . TOPIC_CLOSE_DELAY
2013-01-17 22:09:37 -05:00
2009-08-20 12:46:08 +03:00
def paged ( paged_list_name , per_page ) :
2009-01-05 14:30:08 +02:00
"""
Parse page from GET data and pass it to view . Split the
query set returned from view .
"""
2009-08-20 12:46:08 +03:00
2009-01-05 14:30:08 +02:00
def decorator ( func ) :
def wrapper ( request , * args , * * kwargs ) :
result = func ( request , * args , * * kwargs )
2009-08-20 12:46:08 +03:00
if not isinstance ( result , dict ) or ' paged_qs ' not in result :
2009-01-05 14:30:08 +02:00
return result
try :
page = int ( request . GET . get ( ' page ' , 1 ) )
except ValueError :
page = 1
real_per_page = per_page
#if per_page_var:
#try:
#value = int(request.GET[per_page_var])
#except (ValueError, KeyError):
#pass
#else:
#if value > 0:
#real_per_page = value
from django . core . paginator import Paginator
paginator = Paginator ( result [ ' paged_qs ' ] , real_per_page )
2009-08-27 17:41:42 +03:00
try :
2011-05-03 12:45:46 +03:00
page_obj = paginator . page ( page )
2009-08-27 17:41:42 +03:00
except ( InvalidPage , EmptyPage ) :
raise Http404
2011-05-03 12:45:46 +03:00
result [ paged_list_name ] = page_obj . object_list
result [ ' is_paginated ' ] = page_obj . has_other_pages ( ) ,
result [ ' page_obj ' ] = page_obj ,
2009-01-05 14:30:08 +02:00
result [ ' page ' ] = page
2011-05-03 12:45:46 +03:00
result [ ' page_range ' ] = paginator . page_range ,
2009-01-05 14:30:08 +02:00
result [ ' pages ' ] = paginator . num_pages
2011-05-03 12:45:46 +03:00
result [ ' results_per_page ' ] = paginator . per_page ,
2009-01-05 14:30:08 +02:00
result [ ' request ' ] = request
return result
return wrapper
return decorator
class LazyJSONEncoder ( JSONEncoder ) :
"""
This fing need to save django from crashing .
"""
def default ( self , o ) :
if isinstance ( o , Promise ) :
return force_unicode ( o )
else :
return super ( LazyJSONEncoder , self ) . default ( o )
class JsonResponse ( HttpResponse ) :
"""
HttpResponse subclass that serialize data into JSON format .
"""
def __init__ ( self , data , mimetype = ' application/json ' ) :
json_data = LazyJSONEncoder ( ) . encode ( data )
super ( JsonResponse , self ) . __init__ (
content = json_data , mimetype = mimetype )
2010-01-05 15:56:19 +02:00
2009-01-05 14:30:08 +02:00
def build_form ( Form , _request , GET = False , * args , * * kwargs ) :
"""
2009-07-02 16:01:22 +03:00
Shorcut for building the form instance of given form class
2009-01-05 14:30:08 +02:00
"""
if not GET and ' POST ' == _request . method :
form = Form ( _request . POST , _request . FILES , * args , * * kwargs )
elif GET and ' GET ' == _request . method :
form = Form ( _request . GET , _request . FILES , * args , * * kwargs )
else :
form = Form ( * args , * * kwargs )
return form
2010-01-05 15:56:19 +02:00
2009-01-21 18:28:36 +02:00
class ExcludeTagsHTMLParser ( HTMLParser ) :
"""
Class for html parsing with excluding specified tags .
"""
2011-04-04 12:56:16 +03:00
def __init__ ( self , func , tags = ( ' a ' , ' pre ' , ' span ' ) ) :
2009-01-09 16:11:30 +02:00
HTMLParser . __init__ ( self )
2009-01-21 18:28:36 +02:00
self . func = func
self . is_ignored = False
self . tags = tags
self . html = [ ]
2009-01-09 16:11:30 +02:00
def handle_starttag ( self , tag , attrs ) :
2009-01-21 18:28:36 +02:00
self . html . append ( ' < %s %s > ' % ( tag , self . __html_attrs ( attrs ) ) )
if tag in self . tags :
self . is_ignored = True
2009-01-09 16:11:30 +02:00
def handle_data ( self , data ) :
2009-01-21 18:28:36 +02:00
if not self . is_ignored :
data = self . func ( data )
self . html . append ( data )
2009-01-09 16:11:30 +02:00
def handle_startendtag ( self , tag , attrs ) :
2012-08-08 11:00:28 +03:00
self . html . append ( ' < %s %s /> ' % ( tag , self . __html_attrs ( attrs ) ) )
2009-01-21 18:28:36 +02:00
2009-01-09 16:11:30 +02:00
def handle_endtag ( self , tag ) :
2009-01-21 18:28:36 +02:00
self . is_ignored = False
self . html . append ( ' </ %s > ' % ( tag ) )
2009-01-20 17:42:02 +02:00
def handle_entityref ( self , name ) :
2009-01-21 18:28:36 +02:00
self . html . append ( ' & %s ; ' % name )
2009-01-20 17:42:02 +02:00
def handle_charref ( self , name ) :
2010-01-04 14:17:36 +02:00
self . html . append ( ' &# %s ; ' % name )
2009-01-20 17:42:02 +02:00
2010-01-05 15:56:19 +02:00
def unescape ( self , s ) :
#we don't need unescape data (without this possible XSS-attack)
return s
2009-01-09 16:11:30 +02:00
def __html_attrs ( self , attrs ) :
_attrs = ' '
if attrs :
2012-08-08 11:00:28 +03:00
_attrs = ' %s ' % ( ' ' . join ( [ ( ' %s = " %s " ' % ( k , v ) ) for k , v in attrs ] ) )
2009-01-09 16:11:30 +02:00
return _attrs
2009-01-21 18:28:36 +02:00
2009-01-09 16:11:30 +02:00
def feed ( self , data ) :
HTMLParser . feed ( self , data )
2009-01-21 18:28:36 +02:00
self . html = ' ' . join ( self . html )
2010-01-05 15:56:19 +02:00
2013-01-15 16:00:14 -05:00
def filter_language ( text ) :
"""
Replaces filtered language in the given text with an asterisk .
"""
2013-01-17 17:55:28 -05:00
return re . sub ( forum_settings . LANGUAGE_FILTER , ' * ' , text ) if forum_settings . LANGUAGE_FILTER else text
2013-01-15 16:00:14 -05:00
2009-01-21 18:28:36 +02:00
def _smile_replacer ( data ) :
for smile , path in _SMILES :
data = smile . sub ( path , data )
2009-01-17 14:42:12 +02:00
return data
2012-09-24 14:25:41 +03:00
def smiles ( html ) :
2009-01-21 18:28:36 +02:00
"""
Replace text smiles .
"""
2012-09-24 14:25:41 +03:00
try :
parser = ExcludeTagsHTMLParser ( _smile_replacer )
parser . feed ( html )
smiled_html = parser . html
parser . close ( )
except HTMLParseError :
# HTMLParser from Python <2.7.3 is not robust
# see: http://support.djangobb.org/topic/349/
if settings . DEBUG :
raise
return html
2009-01-21 18:28:36 +02:00
return smiled_html
2009-02-09 20:31:19 +02:00
def paginate ( items , request , per_page , total_count = None ) :
try :
page_number = int ( request . GET . get ( ' page ' , 1 ) )
except ValueError :
page_number = 1
2009-10-19 14:02:27 +03:00
2009-02-09 20:31:19 +02:00
paginator = Paginator ( items , per_page )
pages = paginator . num_pages
2009-08-24 23:37:53 +03:00
try :
paged_list_name = paginator . page ( page_number ) . object_list
except ( InvalidPage , EmptyPage ) :
2010-01-04 14:17:36 +02:00
raise Http404
2012-08-08 11:00:28 +03:00
return pages , paginator , paged_list_name
2009-02-09 20:31:19 +02:00
def set_language ( request , language ) :
"""
Change the language of session of authenticated user .
"""
2011-05-17 14:12:45 +03:00
if check_for_language ( language ) :
request . session [ ' django_language ' ] = language
2010-11-28 17:57:54 +02:00
2013-01-17 20:28:40 -05:00
def convert_text_to_html ( text , profile ) :
markup = profile . markup
2010-11-28 17:57:54 +02:00
if markup == ' bbcode ' :
2013-01-17 20:28:40 -05:00
renderbb = customize_postmarkup ( profile . user . has_perm ( ' djangobb_forum.post_external_links ' ) )
2013-07-26 22:28:22 +00:00
2012-10-22 11:18:19 -04:00
text = renderbb ( text )
2011-02-28 12:17:16 +02:00
elif markup == ' markdown ' :
2010-11-28 17:57:54 +02:00
text = markdown . markdown ( text , safe_mode = ' escape ' )
else :
raise Exception ( ' Invalid markup property: %s ' % markup )
2013-01-17 20:28:40 -05:00
return text
2012-06-05 11:54:31 +03:00
2012-12-05 21:24:42 -05:00
class WhitelistedImgTag ( postmarkup . ImgTag ) :
def render_open ( self , parser , node_index ) :
contents = self . get_contents ( parser )
self . skip_contents ( parser )
# Validate url to avoid any XSS attacks
if self . params :
url = self . params . strip ( )
else :
url = strip_bbcode ( contents )
url = url . replace ( u ' " ' , u " % 22 " ) . strip ( )
if not url :
return u ' '
try :
scheme , netloc , path , params , query , fragment = urlparse ( url )
if not scheme :
url = u ' http:// ' + url
scheme , netloc , path , params , query , fragment = urlparse ( url )
except ValueError :
return u ' '
if scheme . lower ( ) not in ( u ' http ' , u ' https ' , u ' ftp ' ) :
return u ' '
if not re . search ( forum_settings . IMAGE_HOST_WHITELIST , netloc , re . IGNORECASE ) :
raise UnapprovedImageError ( url )
return u ' <img src= " %s " ></img> ' % postmarkup . PostMarkup . standard_replace_no_break ( url )
class UnapprovedImageError ( Exception ) :
def __init__ ( self , url ) :
self . url = url
def user_error ( self ) :
return _ ( ' Sorry, you need to host your images with a service like imageshack.com. Please update your image links or remove all BB code [img] tags. Bad image url: %s ' ) % self . url
def __str__ ( self ) :
return repr ( self . url )
2013-01-19 16:40:07 -05:00
class CSSClassTag ( postmarkup . TagBase ) :
def __init__ ( self , name , className , * * kwargs ) :
2013-01-11 20:46:25 -05:00
postmarkup . TagBase . __init__ ( self , name , inline = True )
2013-01-19 16:40:07 -05:00
self . className = className
2013-01-11 20:46:25 -05:00
def render_open ( self , parser , node_index ) :
2013-01-19 16:40:07 -05:00
return u ' <span class= " %s " > ' % self . className
2013-01-11 20:46:25 -05:00
def render_close ( self , parser , node_index ) :
return u ' </span> '
2013-01-18 19:19:17 -05:00
class RestrictedLinkTag ( postmarkup . TagBase ) :
2013-01-17 20:28:40 -05:00
_safe_chars = frozenset ( u ' ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-=/&?: % &# ' )
_re_domain = re . compile ( r " //([a-z0-9- \ .]+ \ .)?scratch \ .mit \ .edu " , re . UNICODE )
allowed = False
2013-01-18 19:19:17 -05:00
def __init__ ( self , name , * * kwargs ) :
2013-01-17 20:28:40 -05:00
super ( RestrictedLinkTag , self ) . __init__ ( name , inline = True )
def render_open ( self , parser , node_index ) :
tag_data = parser . tag_data
nest_level = tag_data [ u ' link_nest_level ' ] = tag_data . setdefault ( u ' link_nest_level ' , 0 ) + 1
if nest_level > 1 :
return u " "
if self . params :
url = self . params . strip ( )
else :
url = self . get_contents_text ( parser ) . strip ( )
url = postmarkup . PostMarkup . standard_unreplace ( url )
if u ' : ' not in url :
url = u ' http:// ' + url
scheme , uri = url . split ( u ' : ' , 1 )
2013-01-18 19:19:17 -05:00
if scheme not in [ u ' http ' , u ' https ' ] or self . _re_domain . match ( uri . lower ( ) ) is None :
2013-01-17 20:28:40 -05:00
return u ' <span> ' # Prevent smilies from the ":/" in "http://"
def percent_encode ( s ) :
safe_chars = self . _safe_chars
def replace ( c ) :
if c not in safe_chars :
return u " %% %02X " % ord ( c )
else :
return c
return u " " . join ( [ replace ( c ) for c in s ] )
self . allowed = True
return u ' <a href= " %s " > ' % postmarkup . PostMarkup . standard_replace_no_break ( percent_encode ( url ) )
def render_close ( self , parser , node_index ) :
tag_data = parser . tag_data
tag_data [ u ' link_nest_level ' ] - = 1
if tag_data [ u ' link_nest_level ' ] > 0 :
return u ' '
if self . allowed :
return u ' </a> '
else :
return u ' </span> '
2013-01-18 19:19:17 -05:00
class FilteredLinkTag ( postmarkup . TagBase ) :
_safe_chars = frozenset ( u ' ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-=/&?: % &# ' )
_re_youtube = re . compile ( r ' //(?:www \ .)?youtube \ .com/(?:watch \ ?(?:.*&)?v=|v/)([ \ w-]+) ' , re . UNICODE )
allowed = False
def __init__ ( self , name , * * kwargs ) :
super ( FilteredLinkTag , self ) . __init__ ( name , inline = True )
def render_open ( self , parser , node_index ) :
tag_data = parser . tag_data
nest_level = tag_data [ u ' link_nest_level ' ] = tag_data . setdefault ( u ' link_nest_level ' , 0 ) + 1
if nest_level > 1 :
return u " "
if self . params :
url = self . params . strip ( )
else :
url = self . get_contents_text ( parser ) . strip ( )
url = postmarkup . PostMarkup . standard_unreplace ( url )
if u ' : ' not in url :
url = u ' http:// ' + url
scheme , uri = url . split ( u ' : ' , 1 )
if scheme not in [ u ' http ' , u ' https ' , u ' data ' ] :
return u ' <span> '
def percent_encode ( s ) :
safe_chars = self . _safe_chars
def replace ( c ) :
if c not in safe_chars :
return u " %% %02X " % ord ( c )
else :
return c
return u " " . join ( [ replace ( c ) for c in s ] )
self . allowed = True
if scheme != ' data ' :
match = self . _re_youtube . match ( uri )
if match is not None :
return ' <a href= " %s " > ' % reverse ( ' djangobb:show_youtube_video ' , args = ( match . group ( 1 ) , ) )
return u ' <a href= " %s " > ' % postmarkup . PostMarkup . standard_replace_no_break ( percent_encode ( url ) )
def render_close ( self , parser , node_index ) :
tag_data = parser . tag_data
tag_data [ u ' link_nest_level ' ] - = 1
if tag_data [ u ' link_nest_level ' ] > 0 :
return u ' '
if self . allowed :
return u ' </a> '
else :
return u ' </span> '
2013-01-27 18:41:28 -05:00
class QuoteTag ( postmarkup . TagBase ) :
def __init__ ( self , name , * * kwargs ) :
super ( QuoteTag , self ) . __init__ ( name , strip_first_newline = True )
def render_open ( self , parser , node_index ) :
if self . params :
return u ' <blockquote><p class= " bb-quote-author " > %s wrote:</p> ' % ( postmarkup . PostMarkup . standard_replace ( self . params ) )
else :
return u ' <blockquote> '
def render_close ( self , parser , node_index ) :
return u ' </blockquote> '
2013-07-26 22:28:22 +00:00
class ScratchblocksTag ( postmarkup . TagBase ) :
def __init__ ( self , name , * * kwargs ) :
super ( ScratchblocksTag , self ) . __init__ ( name , enclosed = True , strip_first_newline = True )
def render_open ( self , parser , node_index ) :
contents = self . get_contents ( parser ) . strip ( u ' \n ' )
self . skip_contents ( parser )
return u ' <pre class=blocks> %s </pre> ' % postmarkup . PostMarkup . standard_replace ( contents )
2014-02-13 14:06:27 -05:00
2012-10-22 11:32:21 -04:00
# This allows us to control the bb tags
2013-01-17 20:28:40 -05:00
def customize_postmarkup ( allow_external_links ) :
2012-10-22 11:18:19 -04:00
custom_postmarkup = postmarkup . PostMarkup ( )
add_tag = custom_postmarkup . tag_factory . add_tag
custom_postmarkup . tag_factory . set_default_tag ( postmarkup . DefaultTag )
2013-01-27 18:41:28 -05:00
add_tag ( CSSClassTag , ' b ' , ' bb-bold ' )
add_tag ( CSSClassTag , ' i ' , ' bb-italic ' )
2013-01-19 16:40:07 -05:00
add_tag ( CSSClassTag , ' u ' , ' bb-underline ' )
2013-01-27 18:41:28 -05:00
add_tag ( CSSClassTag , ' s ' , ' bb-strikethrough ' )
2012-10-22 11:18:19 -04:00
2013-07-26 22:28:22 +00:00
add_tag ( ScratchblocksTag , ' scratchblocks ' )
2013-01-18 19:19:17 -05:00
add_tag ( FilteredLinkTag if allow_external_links else RestrictedLinkTag , ' url ' )
2012-10-22 11:18:19 -04:00
2013-01-27 18:41:28 -05:00
add_tag ( QuoteTag , ' quote ' )
2012-10-22 11:18:19 -04:00
2013-01-20 15:04:04 -05:00
add_tag ( postmarkup . SearchTag , u ' wp ' ,
2012-10-22 11:18:19 -04:00
u " http://en.wikipedia.org/wiki/Special:Search?search= %s " , u ' wikipedia.com ' , None )
2013-01-20 15:04:04 -05:00
add_tag ( postmarkup . SearchTag , u ' wiki ' ,
u " http://wiki.scratch.mit.edu/wiki/Special:Search?search= %s " , u ' wiki.scratch.mit.edu ' , None )
2012-10-22 11:18:19 -04:00
add_tag ( postmarkup . SearchTag , u ' google ' ,
u " http://www.google.com/search?hl=en&q= %s &btnG=Google+Search " , u ' google.com ' , None )
add_tag ( postmarkup . SearchTag , u ' dictionary ' ,
u " http://dictionary.reference.com/browse/ %s " , u ' dictionary.com ' , None )
add_tag ( postmarkup . SearchTag , u ' dict ' ,
u " http://dictionary.reference.com/browse/ %s " , u ' dictionary.com ' , None )
2012-12-05 21:24:42 -05:00
add_tag ( WhitelistedImgTag , u ' img ' )
2012-10-22 11:18:19 -04:00
add_tag ( postmarkup . ListTag , u ' list ' )
add_tag ( postmarkup . ListItemTag , u ' * ' )
2012-10-22 11:32:21 -04:00
# removed 'size' and replaced it with 'big' and 'small'
2013-01-19 16:40:07 -05:00
add_tag ( CSSClassTag , u ' big ' , u ' bb-big ' )
add_tag ( CSSClassTag , u ' small ' , u ' bb-small ' )
2012-10-22 11:18:19 -04:00
add_tag ( postmarkup . ColorTag , u " color " )
add_tag ( postmarkup . CenterTag , u " center " )
add_tag ( postmarkup . PygmentsCodeTag , u ' code ' , None )
add_tag ( postmarkup . SimpleTag , u " p " , ' p ' )
return custom_postmarkup