├── __init__.py ├── schema.sql ├── app ├── __init__.py ├── helpers │ ├── __init__.py │ ├── misc.py │ ├── utils.py │ └── paging.py ├── models │ ├── __init__.py │ ├── recipients.py │ └── templates.py ├── controllers │ ├── __init__.py │ ├── public.py │ ├── preview_recipients.py │ └── handle_templates.py └── views │ ├── sendmail_form.html │ ├── index.html │ ├── base.html │ └── recipients_preview.html ├── README ├── public ├── img │ ├── favicon.ico │ ├── loading.gif │ └── email_go.png ├── js │ ├── defaults.js │ └── jquery-1.3.2.js └── css │ └── defaults.css ├── TODO ├── application.py ├── config_example.py ├── INSTALL └── LICENSE /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /schema.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/helpers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | A very simple but flexible mass mailer. 2 | 3 | Try a demo: http://mailer.ksikes.net. 4 | -------------------------------------------------------------------------------- /public/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexksikes/mailer/HEAD/public/img/favicon.ico -------------------------------------------------------------------------------- /public/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexksikes/mailer/HEAD/public/img/loading.gif -------------------------------------------------------------------------------- /public/img/email_go.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexksikes/mailer/HEAD/public/img/email_go.png -------------------------------------------------------------------------------- /app/controllers/public.py: -------------------------------------------------------------------------------- 1 | # Author: Alex Ksikes 2 | 3 | import web 4 | import mimetypes 5 | 6 | class public: 7 | def GET(self): 8 | public_dir = 'public' 9 | try: 10 | file_name = web.ctx.path.split('/')[-1] 11 | web.header('Content-type', mime_type(file_name)) 12 | return open(public_dir + web.ctx.path, 'rb').read() 13 | except IOError: 14 | raise web.notfound() 15 | 16 | def mime_type(filename): 17 | return mimetypes.guess_type(filename)[0] or 'application/octet-stream' 18 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | IMPLEMENT: 2 | 3 | - keep a list of message / email address in order to avoid resending to the same email (unless force is checked) 4 | - send a copy to ... 5 | - demo mode should be a switch in config.py 6 | 7 | MORE IDEAS: 8 | 9 | - have recipient lists which are SQL queries. 10 | >> recipients creation should be part of another panel (more advanced one). 11 | >> templates are connected to recipients (variables being used) so it might not be a good idea to disociate the two. 12 | 13 | - have an export data as CSV option. 14 | - need to be able to save templates. 15 | - need to keep rack of exactly which email was sent and to whom. 16 | -------------------------------------------------------------------------------- /app/controllers/preview_recipients.py: -------------------------------------------------------------------------------- 1 | # Author: Alex Ksikes 2 | 3 | import web 4 | import config 5 | 6 | from config import view 7 | 8 | from app.helpers import paging 9 | from app.models import recipients 10 | 11 | import re 12 | 13 | results_per_page = 15 14 | 15 | class browse: 16 | def GET(self): 17 | i = web.input(start=0, sql_query='') 18 | start = int(i.start) 19 | 20 | results, count, columns = recipients.get(i.sql_query, offset=start, limit=results_per_page) 21 | pager = web.storage(paging.get_paging(start, count, 22 | results_per_page=results_per_page, window_size=1)) 23 | 24 | return view.recipients_preview(results, columns, pager, i) 25 | -------------------------------------------------------------------------------- /application.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Author: Alex Ksikes 3 | 4 | import web 5 | import config 6 | import app.controllers 7 | 8 | urls = ( 9 | '/', 'app.controllers.handle_templates.index', 10 | '/send', 'app.controllers.handle_templates.send', 11 | '/recipients', 'app.controllers.preview_recipients.browse', 12 | 13 | '/(?:img|js|css)/.*', 'app.controllers.public.public', 14 | ) 15 | 16 | app = web.application(urls, globals()) 17 | if web.config.email_errors: 18 | app.internalerror = web.emailerrors(web.config.email_errors, web.webapi._InternalError) 19 | 20 | if __name__ == "__main__": 21 | app.run() 22 | -------------------------------------------------------------------------------- /app/views/sendmail_form.html: -------------------------------------------------------------------------------- 1 | $def with (form, success=False, count=0) 2 | 3 | $if success: 4 |
$count email(s) sent.
5 | 6 | 7 |
$get_site_config('mail_sender')
8 | $:form.render_css() 9 | 10 | $# 11 | 12 | $#Test SQL query » 13 | $#
14 | $# $:form.render_css() 15 | $# 16 | $# $# 17 | $#
-------------------------------------------------------------------------------- /app/views/index.html: -------------------------------------------------------------------------------- 1 | $def with (form) 2 | 3 |
4 | 5 |
6 |

Write your template:

7 | 8 |
9 | $:form 10 |
11 |
12 | 13 |
14 |

Choose your recipients:

15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 | 25 |
-------------------------------------------------------------------------------- /app/models/recipients.py: -------------------------------------------------------------------------------- 1 | # Author: Alex Ksikes 2 | 3 | import web 4 | import config 5 | 6 | import MySQLdb, re 7 | 8 | # we are forced to use MySQLdb in order to keep the columns order 9 | def __get_cursor(): 10 | param = config.db_records_settings 11 | return MySQLdb.connect(user=param.user, passwd=param.passwd, db=param.db, use_unicode=True).cursor() 12 | 13 | def get(sql_query, offset, limit): 14 | c = __get_cursor() 15 | 16 | c.execute(sql_query + ' limit %s offset %s' % (limit, offset)) 17 | columns = [x[0] for x in c.description] 18 | results = c.fetchall() 19 | 20 | query_count = re.sub('select .*? from', 'select count(*) from', sql_query, re.I) 21 | c.execute(query_count) 22 | count = c.fetchall()[0][0] 23 | 24 | return (results, count, columns) 25 | 26 | def get_all(sql_query): 27 | db = web.database(**config.db_records_settings) 28 | return db.query(sql_query) -------------------------------------------------------------------------------- /public/js/defaults.js: -------------------------------------------------------------------------------- 1 | function preview_recipients() { 2 | var sql_query = $('#sql_query')[0].value; 3 | $("#recipients_preview").load("/recipients?sql_query="+escape(sql_query)); 4 | } 5 | 6 | function ajax_load(url) { 7 | $("#recipients_preview").load(url); 8 | } 9 | 10 | function send_mails() { 11 | $('#template button').replaceWith('
Sending ...
'); 12 | var data = new Object(); 13 | $('#sendmail_form input, #sendmail_form textarea, #recipients textarea').each( 14 | function() { 15 | /* on firefox javascript considers no value for a checkbox as a checked checkbox */ 16 | if (this.type == 'checkbox') { 17 | data[this.name] = this.checked || ''; 18 | } else { 19 | data[this.name] = this.value; 20 | } 21 | } 22 | ); 23 | $('#sendmail_form').load('/send', data); 24 | return false; 25 | } -------------------------------------------------------------------------------- /app/views/base.html: -------------------------------------------------------------------------------- 1 | $def with (content) 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Mailer 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | $#
21 | $# 25 | $#
26 | 27 |
28 | $:content 29 |
30 | 31 | 33 | 34 | -------------------------------------------------------------------------------- /config_example.py: -------------------------------------------------------------------------------- 1 | # Author: Alex Ksikes 2 | 3 | import web 4 | 5 | from app.helpers import misc 6 | from app.helpers import utils 7 | 8 | # connect to the mailer database. 9 | db = web.database(dbn='mysql', db='mailer', user='user', passwd='password') 10 | 11 | # in development debug error messages and reloader. 12 | web.config.debug = False 13 | 14 | # in develpment template caching is set to false. 15 | cache = True 16 | 17 | # global used template functions. 18 | globals = utils.get_all_functions(misc) 19 | 20 | # the domain where the site is hosted. 21 | site_domain = 'www.your_domain.com' 22 | 23 | # set global base template. 24 | view = web.template.render('app/views', cache=cache, globals=globals) 25 | 26 | # used as a salt. 27 | encryption_key = 'a random string' 28 | 29 | # in production the internal errors could be emailed to us. 30 | web.config.email_errors = '' 31 | 32 | # email of the sender. 33 | # IMPORTANT: make sure SPF and reverse DNS are setup to avoid mails being marked as spam. 34 | mail_sender = 'Mailer sender name ' 35 | 36 | # used for the send a copy feature. 37 | mail_bcc = 'Send a copy to name ' 38 | 39 | # db setting used to browse through your databases. 40 | # IMPORTANT: make sure user only has select rights. 41 | db_records_settings = web.storage(dbn='mysql', db='mlss', user="user", passwd="password") 42 | -------------------------------------------------------------------------------- /app/models/templates.py: -------------------------------------------------------------------------------- 1 | # Author: Alex Ksikes 2 | 3 | import web 4 | import config 5 | from config import db 6 | 7 | from app.models import recipients 8 | 9 | import re 10 | 11 | def send(to_tpl, subject_tpl, message_tpl, sql_query='', reply_to='', send_copy=False): 12 | headers = {} 13 | if reply_to: headers['Reply-To'] = reply_to 14 | 15 | bcc = '' 16 | if send_copy: bcc = config.mail_bcc 17 | 18 | count = 1 19 | if not sql_query: 20 | web.sendmail(config.mail_sender, to_tpl, subject_tpl, message_tpl, bcc=bcc, headers=headers) 21 | else: 22 | recipients_ = recipients.get_all(sql_query) 23 | count = len(recipients_) 24 | for recipient in recipients_: 25 | to = instantiate_from_template(to_tpl, recipient) 26 | subject = instantiate_from_template(subject_tpl, recipient) 27 | message = instantiate_from_template(message_tpl, recipient) 28 | 29 | web.sendmail(config.mail_sender, to, subject, message, bcc=bcc, headers=headers) 30 | return count 31 | 32 | def instantiate_from_template(tpl, recipient): 33 | # we can't do this! web.template.Template('$def with (**recipient)\n %s' % tpl)(**recipient) 34 | tpl = re.sub('\$(\w+)', '$recipient.\\1', tpl, re.S) 35 | tpl = '$def with (recipient)\n%s' % tpl 36 | 37 | return web.template.Template(tpl)(recipient).__body__[:-1] 38 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | 1) Download the latest tar ball: http://github.com/alexksikes/mailer/tarball/master. 2 | 3 | 2) tar xvzf "the tar ball" 4 | 5 | 3) Download install webpy, follow instructions here: http://webpy.org/install 6 | 7 | 4) Setup lighttpd, here is a part your config file: 8 | 9 | ... 10 | 11 | name = "mailer" 12 | script = "path to ./application.py" 13 | 14 | server.document-root = "path to ./public/" 15 | 16 | # password protect access to this site 17 | $HTTP["url"] !~ "" { 18 | auth.require = ( "" => 19 | ( 20 | "method" => "digest", 21 | "realm" => "Authorized users only", 22 | "require" => "valid-user", 23 | )) 24 | } 25 | 26 | url.rewrite += ( 27 | # Commented for development 28 | "^/img/(.*)$" => "/img/$1", 29 | "^/css/(.*)$" => "/css/$1", 30 | "^/js/(.*)$" => "/js/$1", 31 | 32 | "^/(.*)$" => script + "/$1", 33 | ) 34 | 35 | fastcgi.server += ( script => 36 | (( 37 | "socket" => "/tmp/" + name + var.PID + ".socket", 38 | "bin-path" => script, 39 | "check-local" => "disable", 40 | "max-procs" => 1, 41 | "bin-environment" => ( 42 | "REAL_SCRIPT_NAME" => "" 43 | ), 44 | )) 45 | ) 46 | 47 | ... 48 | 49 | 5) Setup your database: mysql -p mailer < ./schema.sql 50 | 51 | 6) Go over your iste settings: go over config_example.py and rename it config.py 52 | 53 | 7) Restart lighttpd and you're done! 54 | -------------------------------------------------------------------------------- /app/views/recipients_preview.html: -------------------------------------------------------------------------------- 1 | $def with (results, columns, pager, inputs) 2 | 3 | $def show_paging(): 4 | $if pager.leftmost_a or pager.leftmost_a == 0: 5 | << First 6 | $if pager.left_a or pager.left_a == 0: 7 | < Prev 8 | $(pager.start + 1) - 9 | $if pager.right_a: 10 | $pager.right_a 11 | $else: 12 | $pager.max_results 13 | of $pager.max_results 14 | $if pager.right_a: 15 | Next > 16 | $if pager.rightmost_a: 17 | Last >> 18 | 19 | $def show_table_header(): 20 | 21 | $for column in columns: 22 | \$$column 23 | 24 | 25 |

Variables you may now use in your template:

26 | 27 |
28 | $for column in columns: 29 | \$$column 30 |
31 | 32 |

Your email will be sent to everyone on this list:

33 |
34 | $:show_paging() 35 |
36 | 37 |
38 | 39 | $:show_table_header() 40 | $for row in results: 41 | 42 | $#$for value in row.values(): 43 | $for value in row: 44 | 48 | 49 |
45 | $if value: $value 46 | $else: - 47 |
50 |
-------------------------------------------------------------------------------- /app/helpers/misc.py: -------------------------------------------------------------------------------- 1 | # Author: Alex Ksikes 2 | 3 | import web 4 | import config 5 | 6 | import re, urlparse, datetime, urllib, utils, string 7 | 8 | def format_date(d, f): 9 | return d.strftime(f) 10 | 11 | def url_quote(url): 12 | return web.urlquote(url) 13 | 14 | def html_quote(html): 15 | return web.htmlquote(url) 16 | 17 | def url_encode(query, clean=True, doseq=True, **kw): 18 | query = web.dictadd(query, kw) 19 | if clean is True: 20 | for q, v in query.items(): 21 | if not v: 22 | del query[q] 23 | return urllib.urlencode(query, doseq) 24 | 25 | def cut_length(s, max=40): 26 | if len(s) > max: 27 | s = s[0:max] + '...' 28 | return s 29 | 30 | def get_nice_url(url): 31 | host, path = urlparse.urlparse(url)[1:3] 32 | if path == '/': 33 | path = '' 34 | return cut_length(host+path) 35 | 36 | def capitalize_first(str): 37 | if not str: 38 | str = '' 39 | return ' '.join(map(string.capitalize, str.lower().split())) 40 | 41 | def text2html(s): 42 | s = replace_breaks(s) 43 | s = replace_indents(s) 44 | return replace_links(s) 45 | 46 | def replace_breaks(s): 47 | return re.sub('\n', '
', s) 48 | 49 | def replace_indents(s): 50 | s = re.sub('\t', 4*' ', s) 51 | return re.sub('\s{2}', ' '*2, s) 52 | 53 | def replace_links(s): 54 | return re.sub('(http://[^\s]+)', r'' + get_nice_url(r'\1') + '', s, re.I) 55 | 56 | # we may need to get months ago as well 57 | def how_long(d): 58 | return web.datestr(d, datetime.datetime.now()) 59 | 60 | def split(pattern, str): 61 | return re.split(pattern, str) 62 | 63 | def get_site_config(attr): 64 | return getattr(config, attr) 65 | -------------------------------------------------------------------------------- /app/controllers/handle_templates.py: -------------------------------------------------------------------------------- 1 | # Author: Alex Ksikes 2 | 3 | import web 4 | import config 5 | 6 | from config import view 7 | from web import form 8 | 9 | from app.models import templates 10 | from app.helpers import utils 11 | 12 | sendmail_form = \ 13 | utils.Form( 14 | form.Textbox('reply_to', 15 | description='Reply to: '), 16 | form.Textbox('to', 17 | form.notnull, 18 | description='To: ', 19 | pre=''), 20 | form.Textbox('subject', 21 | form.notnull, 22 | description='Subject: ', 23 | pre=''), 24 | form.Textarea('message', 25 | form.notnull, 26 | description='Message: ', 27 | pre=''), 28 | form.Checkbox('send_copy', 29 | description='', 30 | post='Send a copy to: %s' % web.htmlquote(config.mail_bcc)), 31 | form.Checkbox('force_resend', 32 | description='', 33 | post='Force resend to previously emailed recipients.')) 34 | 35 | class index: 36 | def GET(self): 37 | return view.base(view.index(view.sendmail_form(sendmail_form()))) 38 | 39 | class send: 40 | def POST(self): 41 | f = sendmail_form() 42 | 43 | count = 0 44 | if f.validates(): 45 | d = f.d; d.sql_query = web.input().sql_query 46 | count = templates.send(d.to, d.subject, d.message, d.sql_query, d.reply_to, d.send_copy) 47 | return view.sendmail_form(f, success=f.valid, count=count) -------------------------------------------------------------------------------- /app/helpers/utils.py: -------------------------------------------------------------------------------- 1 | # Author: Alex Ksikes 2 | 3 | import web 4 | import config 5 | 6 | import md5, time, types 7 | 8 | def dict_remove(d, *keys): 9 | for k in keys: 10 | if d.has_key(k): 11 | del d[k] 12 | 13 | def make_unique_md5(): 14 | return md5.md5(time.ctime() + config.encryption_key).hexdigest() 15 | 16 | def get_all_functions(module): 17 | functions = {} 18 | for f in [module.__dict__.get(a) for a in dir(module) 19 | if isinstance(module.__dict__.get(a), types.FunctionType)]: 20 | functions[f.__name__] = f 21 | return functions 22 | 23 | def sqlands(left, lst): 24 | """Similar to webpy sqlors but for ands""" 25 | if isinstance(lst, web.utils.iters): 26 | lst = list(lst) 27 | ln = len(lst) 28 | if ln == 0: 29 | return web.SQLQuery("1!=2") 30 | if ln == 1: 31 | lst = lst[0] 32 | if isinstance(lst, web.utils.iters): 33 | return web.SQLQuery(['('] + 34 | sum([[left, web.sqlparam(x), ' AND '] for x in lst], []) + 35 | ['1!=2)'] 36 | ) 37 | else: 38 | return left + web.sqlparam(lst) 39 | 40 | def get_ip(): 41 | return web.ctx.get('ip', '000.000.000.000') 42 | 43 | class Form(web.form.Form): 44 | def render_css(self): 45 | out = [] 46 | out.append(self.rendernote(self.note)) 47 | for i in self.inputs: 48 | if i.note: 49 | out.append('
' % i.id) 50 | else: 51 | out.append('
' % i.id) 52 | out.append('' % (i.id, web.websafe(i.description))) 53 | out.append(i.pre) 54 | out.append(i.render()) 55 | out.append(i.post) 56 | out.append('
\n') 57 | return ''.join(out) -------------------------------------------------------------------------------- /app/helpers/paging.py: -------------------------------------------------------------------------------- 1 | # Author : Alex Ksikes 2 | 3 | import urllib 4 | 5 | def get_paging(start, max_results, query=False, results_per_page=15, window_size=15): 6 | c_page = start / results_per_page + 1 7 | if not start: 8 | c_page = 1 9 | nb_pages = max_results / results_per_page 10 | if max_results % results_per_page != 0: 11 | nb_pages += 1 12 | 13 | left_a = right_a = '' 14 | if c_page > 1: 15 | left_a = (c_page - 2) * results_per_page 16 | if c_page < nb_pages: 17 | right_a = start + results_per_page 18 | 19 | leftmost_a = rightmost_a = '' 20 | if (c_page - 3) >= 0: 21 | leftmost_a = 0 22 | if (c_page + 1) < nb_pages: 23 | rightmost_a = (nb_pages -1) * results_per_page 24 | 25 | left = c_page - window_size / 2 26 | if left < 1: 27 | left = 1 28 | right = left + window_size - 1 29 | if right > nb_pages: 30 | right = nb_pages 31 | 32 | pages = [] 33 | for i in range(left, right + 1): 34 | pages.append({ 35 | 'number' : i, 36 | 'start' : (i - 1) * results_per_page 37 | }) 38 | 39 | return { 40 | 'start' : start, 41 | 'max_results' : max_results, 42 | 'c_page' : c_page, 43 | 'nb_pages' : nb_pages, 44 | 'pages' : pages, 45 | 'leftmost_a' : leftmost_a, 46 | 'left_a' : left_a, 47 | 'right_a' : right_a, 48 | 'rightmost_a' : rightmost_a, 49 | 'query_enc' : query and urllib.quote(query) or '' 50 | } 51 | 52 | def get_paging_results(start, max_results, id, results, results_per_page): 53 | index = 0 54 | results = list(results) 55 | for i, r in enumerate(results): 56 | if r.id == id: index = i 57 | pager = dict(left_start=start, right_start=start, max_results=max_results, 58 | number=start + index, left=False, middle=results[index], right=False) 59 | 60 | if index > 0 or start > 0: 61 | pager['left'] = results[index-1] 62 | if index < len(results) - 1: 63 | pager['right'] = results[index+1] 64 | if index == results_per_page - 1: 65 | pager['right_start'] = start + results_per_page 66 | if index == 0 and start > 0: 67 | pager['left_start'] = start - results_per_page 68 | if start != 0: 69 | pager['number'] = start + index -1 70 | 71 | return pager 72 | 73 | -------------------------------------------------------------------------------- /public/css/defaults.css: -------------------------------------------------------------------------------- 1 | /* defaults */ 2 | body, h3, ul, li, form { 3 | margin: 0px; 4 | padding: 0px; 5 | } 6 | 7 | body, form { 8 | font-family: arial, sans-serif; 9 | font-size: 13px; 10 | line-height: 19px; 11 | } 12 | 13 | a { 14 | color : #0000CC; 15 | } 16 | 17 | a img { 18 | border: 0pt; 19 | } 20 | 21 | strong.wrong { 22 | color: red; 23 | float: right; 24 | } 25 | 26 | div.wrong input, div.wrong textarea { 27 | background: #FFDFDF; 28 | } 29 | 30 | .success { 31 | background-color: #FFD37F; 32 | text-align: center; 33 | padding: 5px; 34 | -moz-border-radius: 5px; 35 | font-weight: bold; 36 | /* margin-bottom: 10px;*/ 37 | position: absolute; 38 | top: 5px; 39 | right: 20px; 40 | width: 235px; 41 | } 42 | 43 | a.animated { 44 | text-decoration: none; 45 | } 46 | 47 | a.animated:hover { 48 | text-decoration: underline; 49 | } 50 | /* defaults */ 51 | 52 | /* forms defaults */ 53 | form label, .label { 54 | color: #0066CC; 55 | display: block; 56 | margin: 3px 0px; 57 | cursor: pointer; 58 | font-weight: bold; 59 | } 60 | 61 | form label.help { 62 | color: #7F7F7F; 63 | font-weight: normal; 64 | } 65 | 66 | #form input, form textarea, form select { 67 | form textarea, form select { 68 | width: 100%; 69 | } 70 | 71 | #sendmail_form div { 72 | margin-bottom: 15px; 73 | } 74 | 75 | textarea { 76 | width: 100%; 77 | height: 350px; 78 | } 79 | /* forms defaults */ 80 | 81 | /* html table */ 82 | table { 83 | /* IE need to apply at the table level */ 84 | empty-cells : show; 85 | border-collapse : collapse; 86 | width: 100%; 87 | /*table-layout: fixed;*/ 88 | } 89 | 90 | td, th { 91 | /* width: 100%; */ 92 | font-size : 90%; 93 | white-space : nowrap; 94 | /* padding-right: 10px; 95 | max-width: 185px;*/ 96 | overflow: hidden; 97 | } 98 | 99 | th { 100 | font-weight: normal; 101 | background-color: #EFEFEF; 102 | text-align: left; 103 | } 104 | /* html table */ 105 | 106 | /* sql query */ 107 | #sql_query { 108 | height: 50px; 109 | } 110 | 111 | #test_sql_query { 112 | /* position: absolute; 113 | right: 0px; 114 | top: 5px;*/ 115 | float: right; 116 | font-weight: bold; 117 | font-size: 90%; 118 | } 119 | /* sql query */ 120 | 121 | .content { 122 | padding: 15px; 123 | overflow: auto; 124 | } 125 | 126 | .fixed_input { 127 | border: 2px solid gray; 128 | padding: 3px; 129 | margin-bottom: 15px; 130 | } 131 | 132 | #recipients .template_variables { 133 | border: 2px dotted gray; 134 | padding: 5px; 135 | margin-bottom: 15px; 136 | -moz-border-radius: 5px; 137 | font-weight: bold; 138 | } 139 | 140 | #template { 141 | float: left; 142 | /*width: 400px;*/ 143 | width: 30%; 144 | margin-right: 2%; 145 | } 146 | 147 | #recipients { 148 | /*position: absolute; 149 | margin-left: 420px; 150 | padding: 0px 15px;*/ 151 | padding-left: 2%; 152 | border-left: 2px dotted grey; 153 | width: 65%; 154 | float: left; 155 | } 156 | 157 | #recipients .paging { 158 | float: right; 159 | padding: 2px; 160 | font-weight: bold; 161 | font-size: 90%; 162 | } 163 | 164 | #recipients table td, #recipients table th { 165 | border-bottom : 1px dotted; 166 | padding-top: 3px; 167 | padding-right: 10px; 168 | } 169 | 170 | #recipients .preview_title { 171 | float: left; 172 | margin: 0px; 173 | } 174 | 175 | #recipients .recipients_table { 176 | clear: both; 177 | overflow: scroll; 178 | } 179 | 180 | .sending { 181 | width: 90px; 182 | padding: 3px; 183 | background: url(/img/loading.gif) no-repeat right; 184 | } 185 | 186 | /* checkboxes */ 187 | form #send_copy, form #force_resend { 188 | width: 15px; 189 | margin: 0px 3px 0px 0px; 190 | } 191 | /* checkboxes */ 192 | 193 | #recipients button { 194 | margin: 15px 0px; 195 | } 196 | 197 | /* top menu */ 198 | .topmenu { 199 | border-bottom: 1px solid #C0C0C0; 200 | height: 25px; 201 | line-height: 25px; 202 | margin-bottom: 15px; 203 | position: relative; 204 | z-index: 2; 205 | } 206 | 207 | .topmenu ul { 208 | font-size: 12px; 209 | margin: -2px 3px 0; 210 | position: absolute; 211 | right: 0; 212 | top: 3px; 213 | } 214 | 215 | .topmenu li { 216 | border-right: 1px solid #C0C0C0; 217 | display: inline; 218 | padding:0px 10px; 219 | } 220 | 221 | .topmenu li.last { 222 | ul.extlinks li.last {defaults.css (line 133) 223 | border:medium none; 224 | padding-right:0; 225 | } 226 | /* top menu */ 227 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published by 637 | the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . -------------------------------------------------------------------------------- /public/js/jquery-1.3.2.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery JavaScript Library v1.3.2 3 | * http://jquery.com/ 4 | * 5 | * Copyright (c) 2009 John Resig 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://docs.jquery.com/License 8 | * 9 | * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) 10 | * Revision: 6246 11 | */ 12 | (function(){ 13 | 14 | var 15 | // Will speed up references to window, and allows munging its name. 16 | window = this, 17 | // Will speed up references to undefined, and allows munging its name. 18 | undefined, 19 | // Map over jQuery in case of overwrite 20 | _jQuery = window.jQuery, 21 | // Map over the $ in case of overwrite 22 | _$ = window.$, 23 | 24 | jQuery = window.jQuery = window.$ = function( selector, context ) { 25 | // The jQuery object is actually just the init constructor 'enhanced' 26 | return new jQuery.fn.init( selector, context ); 27 | }, 28 | 29 | // A simple way to check for HTML strings or ID strings 30 | // (both of which we optimize for) 31 | quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/, 32 | // Is it a simple selector 33 | isSimple = /^.[^:#\[\.,]*$/; 34 | 35 | jQuery.fn = jQuery.prototype = { 36 | init: function( selector, context ) { 37 | // Make sure that a selection was provided 38 | selector = selector || document; 39 | 40 | // Handle $(DOMElement) 41 | if ( selector.nodeType ) { 42 | this[0] = selector; 43 | this.length = 1; 44 | this.context = selector; 45 | return this; 46 | } 47 | // Handle HTML strings 48 | if ( typeof selector === "string" ) { 49 | // Are we dealing with HTML string or an ID? 50 | var match = quickExpr.exec( selector ); 51 | 52 | // Verify a match, and that no context was specified for #id 53 | if ( match && (match[1] || !context) ) { 54 | 55 | // HANDLE: $(html) -> $(array) 56 | if ( match[1] ) 57 | selector = jQuery.clean( [ match[1] ], context ); 58 | 59 | // HANDLE: $("#id") 60 | else { 61 | var elem = document.getElementById( match[3] ); 62 | 63 | // Handle the case where IE and Opera return items 64 | // by name instead of ID 65 | if ( elem && elem.id != match[3] ) 66 | return jQuery().find( selector ); 67 | 68 | // Otherwise, we inject the element directly into the jQuery object 69 | var ret = jQuery( elem || [] ); 70 | ret.context = document; 71 | ret.selector = selector; 72 | return ret; 73 | } 74 | 75 | // HANDLE: $(expr, [context]) 76 | // (which is just equivalent to: $(content).find(expr) 77 | } else 78 | return jQuery( context ).find( selector ); 79 | 80 | // HANDLE: $(function) 81 | // Shortcut for document ready 82 | } else if ( jQuery.isFunction( selector ) ) 83 | return jQuery( document ).ready( selector ); 84 | 85 | // Make sure that old selector state is passed along 86 | if ( selector.selector && selector.context ) { 87 | this.selector = selector.selector; 88 | this.context = selector.context; 89 | } 90 | 91 | return this.setArray(jQuery.isArray( selector ) ? 92 | selector : 93 | jQuery.makeArray(selector)); 94 | }, 95 | 96 | // Start with an empty selector 97 | selector: "", 98 | 99 | // The current version of jQuery being used 100 | jquery: "1.3.2", 101 | 102 | // The number of elements contained in the matched element set 103 | size: function() { 104 | return this.length; 105 | }, 106 | 107 | // Get the Nth element in the matched element set OR 108 | // Get the whole matched element set as a clean array 109 | get: function( num ) { 110 | return num === undefined ? 111 | 112 | // Return a 'clean' array 113 | Array.prototype.slice.call( this ) : 114 | 115 | // Return just the object 116 | this[ num ]; 117 | }, 118 | 119 | // Take an array of elements and push it onto the stack 120 | // (returning the new matched element set) 121 | pushStack: function( elems, name, selector ) { 122 | // Build a new jQuery matched element set 123 | var ret = jQuery( elems ); 124 | 125 | // Add the old object onto the stack (as a reference) 126 | ret.prevObject = this; 127 | 128 | ret.context = this.context; 129 | 130 | if ( name === "find" ) 131 | ret.selector = this.selector + (this.selector ? " " : "") + selector; 132 | else if ( name ) 133 | ret.selector = this.selector + "." + name + "(" + selector + ")"; 134 | 135 | // Return the newly-formed element set 136 | return ret; 137 | }, 138 | 139 | // Force the current matched set of elements to become 140 | // the specified array of elements (destroying the stack in the process) 141 | // You should use pushStack() in order to do this, but maintain the stack 142 | setArray: function( elems ) { 143 | // Resetting the length to 0, then using the native Array push 144 | // is a super-fast way to populate an object with array-like properties 145 | this.length = 0; 146 | Array.prototype.push.apply( this, elems ); 147 | 148 | return this; 149 | }, 150 | 151 | // Execute a callback for every element in the matched set. 152 | // (You can seed the arguments with an array of args, but this is 153 | // only used internally.) 154 | each: function( callback, args ) { 155 | return jQuery.each( this, callback, args ); 156 | }, 157 | 158 | // Determine the position of an element within 159 | // the matched set of elements 160 | index: function( elem ) { 161 | // Locate the position of the desired element 162 | return jQuery.inArray( 163 | // If it receives a jQuery object, the first element is used 164 | elem && elem.jquery ? elem[0] : elem 165 | , this ); 166 | }, 167 | 168 | attr: function( name, value, type ) { 169 | var options = name; 170 | 171 | // Look for the case where we're accessing a style value 172 | if ( typeof name === "string" ) 173 | if ( value === undefined ) 174 | return this[0] && jQuery[ type || "attr" ]( this[0], name ); 175 | 176 | else { 177 | options = {}; 178 | options[ name ] = value; 179 | } 180 | 181 | // Check to see if we're setting style values 182 | return this.each(function(i){ 183 | // Set all the styles 184 | for ( name in options ) 185 | jQuery.attr( 186 | type ? 187 | this.style : 188 | this, 189 | name, jQuery.prop( this, options[ name ], type, i, name ) 190 | ); 191 | }); 192 | }, 193 | 194 | css: function( key, value ) { 195 | // ignore negative width and height values 196 | if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) 197 | value = undefined; 198 | return this.attr( key, value, "curCSS" ); 199 | }, 200 | 201 | text: function( text ) { 202 | if ( typeof text !== "object" && text != null ) 203 | return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); 204 | 205 | var ret = ""; 206 | 207 | jQuery.each( text || this, function(){ 208 | jQuery.each( this.childNodes, function(){ 209 | if ( this.nodeType != 8 ) 210 | ret += this.nodeType != 1 ? 211 | this.nodeValue : 212 | jQuery.fn.text( [ this ] ); 213 | }); 214 | }); 215 | 216 | return ret; 217 | }, 218 | 219 | wrapAll: function( html ) { 220 | if ( this[0] ) { 221 | // The elements to wrap the target around 222 | var wrap = jQuery( html, this[0].ownerDocument ).clone(); 223 | 224 | if ( this[0].parentNode ) 225 | wrap.insertBefore( this[0] ); 226 | 227 | wrap.map(function(){ 228 | var elem = this; 229 | 230 | while ( elem.firstChild ) 231 | elem = elem.firstChild; 232 | 233 | return elem; 234 | }).append(this); 235 | } 236 | 237 | return this; 238 | }, 239 | 240 | wrapInner: function( html ) { 241 | return this.each(function(){ 242 | jQuery( this ).contents().wrapAll( html ); 243 | }); 244 | }, 245 | 246 | wrap: function( html ) { 247 | return this.each(function(){ 248 | jQuery( this ).wrapAll( html ); 249 | }); 250 | }, 251 | 252 | append: function() { 253 | return this.domManip(arguments, true, function(elem){ 254 | if (this.nodeType == 1) 255 | this.appendChild( elem ); 256 | }); 257 | }, 258 | 259 | prepend: function() { 260 | return this.domManip(arguments, true, function(elem){ 261 | if (this.nodeType == 1) 262 | this.insertBefore( elem, this.firstChild ); 263 | }); 264 | }, 265 | 266 | before: function() { 267 | return this.domManip(arguments, false, function(elem){ 268 | this.parentNode.insertBefore( elem, this ); 269 | }); 270 | }, 271 | 272 | after: function() { 273 | return this.domManip(arguments, false, function(elem){ 274 | this.parentNode.insertBefore( elem, this.nextSibling ); 275 | }); 276 | }, 277 | 278 | end: function() { 279 | return this.prevObject || jQuery( [] ); 280 | }, 281 | 282 | // For internal use only. 283 | // Behaves like an Array's method, not like a jQuery method. 284 | push: [].push, 285 | sort: [].sort, 286 | splice: [].splice, 287 | 288 | find: function( selector ) { 289 | if ( this.length === 1 ) { 290 | var ret = this.pushStack( [], "find", selector ); 291 | ret.length = 0; 292 | jQuery.find( selector, this[0], ret ); 293 | return ret; 294 | } else { 295 | return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){ 296 | return jQuery.find( selector, elem ); 297 | })), "find", selector ); 298 | } 299 | }, 300 | 301 | clone: function( events ) { 302 | // Do the clone 303 | var ret = this.map(function(){ 304 | if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { 305 | // IE copies events bound via attachEvent when 306 | // using cloneNode. Calling detachEvent on the 307 | // clone will also remove the events from the orignal 308 | // In order to get around this, we use innerHTML. 309 | // Unfortunately, this means some modifications to 310 | // attributes in IE that are actually only stored 311 | // as properties will not be copied (such as the 312 | // the name attribute on an input). 313 | var html = this.outerHTML; 314 | if ( !html ) { 315 | var div = this.ownerDocument.createElement("div"); 316 | div.appendChild( this.cloneNode(true) ); 317 | html = div.innerHTML; 318 | } 319 | 320 | return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0]; 321 | } else 322 | return this.cloneNode(true); 323 | }); 324 | 325 | // Copy the events from the original to the clone 326 | if ( events === true ) { 327 | var orig = this.find("*").andSelf(), i = 0; 328 | 329 | ret.find("*").andSelf().each(function(){ 330 | if ( this.nodeName !== orig[i].nodeName ) 331 | return; 332 | 333 | var events = jQuery.data( orig[i], "events" ); 334 | 335 | for ( var type in events ) { 336 | for ( var handler in events[ type ] ) { 337 | jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); 338 | } 339 | } 340 | 341 | i++; 342 | }); 343 | } 344 | 345 | // Return the cloned set 346 | return ret; 347 | }, 348 | 349 | filter: function( selector ) { 350 | return this.pushStack( 351 | jQuery.isFunction( selector ) && 352 | jQuery.grep(this, function(elem, i){ 353 | return selector.call( elem, i ); 354 | }) || 355 | 356 | jQuery.multiFilter( selector, jQuery.grep(this, function(elem){ 357 | return elem.nodeType === 1; 358 | }) ), "filter", selector ); 359 | }, 360 | 361 | closest: function( selector ) { 362 | var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null, 363 | closer = 0; 364 | 365 | return this.map(function(){ 366 | var cur = this; 367 | while ( cur && cur.ownerDocument ) { 368 | if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) { 369 | jQuery.data(cur, "closest", closer); 370 | return cur; 371 | } 372 | cur = cur.parentNode; 373 | closer++; 374 | } 375 | }); 376 | }, 377 | 378 | not: function( selector ) { 379 | if ( typeof selector === "string" ) 380 | // test special case where just one selector is passed in 381 | if ( isSimple.test( selector ) ) 382 | return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector ); 383 | else 384 | selector = jQuery.multiFilter( selector, this ); 385 | 386 | var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; 387 | return this.filter(function() { 388 | return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; 389 | }); 390 | }, 391 | 392 | add: function( selector ) { 393 | return this.pushStack( jQuery.unique( jQuery.merge( 394 | this.get(), 395 | typeof selector === "string" ? 396 | jQuery( selector ) : 397 | jQuery.makeArray( selector ) 398 | ))); 399 | }, 400 | 401 | is: function( selector ) { 402 | return !!selector && jQuery.multiFilter( selector, this ).length > 0; 403 | }, 404 | 405 | hasClass: function( selector ) { 406 | return !!selector && this.is( "." + selector ); 407 | }, 408 | 409 | val: function( value ) { 410 | if ( value === undefined ) { 411 | var elem = this[0]; 412 | 413 | if ( elem ) { 414 | if( jQuery.nodeName( elem, 'option' ) ) 415 | return (elem.attributes.value || {}).specified ? elem.value : elem.text; 416 | 417 | // We need to handle select boxes special 418 | if ( jQuery.nodeName( elem, "select" ) ) { 419 | var index = elem.selectedIndex, 420 | values = [], 421 | options = elem.options, 422 | one = elem.type == "select-one"; 423 | 424 | // Nothing was selected 425 | if ( index < 0 ) 426 | return null; 427 | 428 | // Loop through all the selected options 429 | for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { 430 | var option = options[ i ]; 431 | 432 | if ( option.selected ) { 433 | // Get the specifc value for the option 434 | value = jQuery(option).val(); 435 | 436 | // We don't need an array for one selects 437 | if ( one ) 438 | return value; 439 | 440 | // Multi-Selects return an array 441 | values.push( value ); 442 | } 443 | } 444 | 445 | return values; 446 | } 447 | 448 | // Everything else, we just grab the value 449 | return (elem.value || "").replace(/\r/g, ""); 450 | 451 | } 452 | 453 | return undefined; 454 | } 455 | 456 | if ( typeof value === "number" ) 457 | value += ''; 458 | 459 | return this.each(function(){ 460 | if ( this.nodeType != 1 ) 461 | return; 462 | 463 | if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) ) 464 | this.checked = (jQuery.inArray(this.value, value) >= 0 || 465 | jQuery.inArray(this.name, value) >= 0); 466 | 467 | else if ( jQuery.nodeName( this, "select" ) ) { 468 | var values = jQuery.makeArray(value); 469 | 470 | jQuery( "option", this ).each(function(){ 471 | this.selected = (jQuery.inArray( this.value, values ) >= 0 || 472 | jQuery.inArray( this.text, values ) >= 0); 473 | }); 474 | 475 | if ( !values.length ) 476 | this.selectedIndex = -1; 477 | 478 | } else 479 | this.value = value; 480 | }); 481 | }, 482 | 483 | html: function( value ) { 484 | return value === undefined ? 485 | (this[0] ? 486 | this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") : 487 | null) : 488 | this.empty().append( value ); 489 | }, 490 | 491 | replaceWith: function( value ) { 492 | return this.after( value ).remove(); 493 | }, 494 | 495 | eq: function( i ) { 496 | return this.slice( i, +i + 1 ); 497 | }, 498 | 499 | slice: function() { 500 | return this.pushStack( Array.prototype.slice.apply( this, arguments ), 501 | "slice", Array.prototype.slice.call(arguments).join(",") ); 502 | }, 503 | 504 | map: function( callback ) { 505 | return this.pushStack( jQuery.map(this, function(elem, i){ 506 | return callback.call( elem, i, elem ); 507 | })); 508 | }, 509 | 510 | andSelf: function() { 511 | return this.add( this.prevObject ); 512 | }, 513 | 514 | domManip: function( args, table, callback ) { 515 | if ( this[0] ) { 516 | var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(), 517 | scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ), 518 | first = fragment.firstChild; 519 | 520 | if ( first ) 521 | for ( var i = 0, l = this.length; i < l; i++ ) 522 | callback.call( root(this[i], first), this.length > 1 || i > 0 ? 523 | fragment.cloneNode(true) : fragment ); 524 | 525 | if ( scripts ) 526 | jQuery.each( scripts, evalScript ); 527 | } 528 | 529 | return this; 530 | 531 | function root( elem, cur ) { 532 | return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ? 533 | (elem.getElementsByTagName("tbody")[0] || 534 | elem.appendChild(elem.ownerDocument.createElement("tbody"))) : 535 | elem; 536 | } 537 | } 538 | }; 539 | 540 | // Give the init function the jQuery prototype for later instantiation 541 | jQuery.fn.init.prototype = jQuery.fn; 542 | 543 | function evalScript( i, elem ) { 544 | if ( elem.src ) 545 | jQuery.ajax({ 546 | url: elem.src, 547 | async: false, 548 | dataType: "script" 549 | }); 550 | 551 | else 552 | jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); 553 | 554 | if ( elem.parentNode ) 555 | elem.parentNode.removeChild( elem ); 556 | } 557 | 558 | function now(){ 559 | return +new Date; 560 | } 561 | 562 | jQuery.extend = jQuery.fn.extend = function() { 563 | // copy reference to target object 564 | var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; 565 | 566 | // Handle a deep copy situation 567 | if ( typeof target === "boolean" ) { 568 | deep = target; 569 | target = arguments[1] || {}; 570 | // skip the boolean and the target 571 | i = 2; 572 | } 573 | 574 | // Handle case when target is a string or something (possible in deep copy) 575 | if ( typeof target !== "object" && !jQuery.isFunction(target) ) 576 | target = {}; 577 | 578 | // extend jQuery itself if only one argument is passed 579 | if ( length == i ) { 580 | target = this; 581 | --i; 582 | } 583 | 584 | for ( ; i < length; i++ ) 585 | // Only deal with non-null/undefined values 586 | if ( (options = arguments[ i ]) != null ) 587 | // Extend the base object 588 | for ( var name in options ) { 589 | var src = target[ name ], copy = options[ name ]; 590 | 591 | // Prevent never-ending loop 592 | if ( target === copy ) 593 | continue; 594 | 595 | // Recurse if we're merging object values 596 | if ( deep && copy && typeof copy === "object" && !copy.nodeType ) 597 | target[ name ] = jQuery.extend( deep, 598 | // Never move original objects, clone them 599 | src || ( copy.length != null ? [ ] : { } ) 600 | , copy ); 601 | 602 | // Don't bring in undefined values 603 | else if ( copy !== undefined ) 604 | target[ name ] = copy; 605 | 606 | } 607 | 608 | // Return the modified object 609 | return target; 610 | }; 611 | 612 | // exclude the following css properties to add px 613 | var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, 614 | // cache defaultView 615 | defaultView = document.defaultView || {}, 616 | toString = Object.prototype.toString; 617 | 618 | jQuery.extend({ 619 | noConflict: function( deep ) { 620 | window.$ = _$; 621 | 622 | if ( deep ) 623 | window.jQuery = _jQuery; 624 | 625 | return jQuery; 626 | }, 627 | 628 | // See test/unit/core.js for details concerning isFunction. 629 | // Since version 1.3, DOM methods and functions like alert 630 | // aren't supported. They return false on IE (#2968). 631 | isFunction: function( obj ) { 632 | return toString.call(obj) === "[object Function]"; 633 | }, 634 | 635 | isArray: function( obj ) { 636 | return toString.call(obj) === "[object Array]"; 637 | }, 638 | 639 | // check if an element is in a (or is an) XML document 640 | isXMLDoc: function( elem ) { 641 | return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || 642 | !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument ); 643 | }, 644 | 645 | // Evalulates a script in a global context 646 | globalEval: function( data ) { 647 | if ( data && /\S/.test(data) ) { 648 | // Inspired by code by Andrea Giammarchi 649 | // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html 650 | var head = document.getElementsByTagName("head")[0] || document.documentElement, 651 | script = document.createElement("script"); 652 | 653 | script.type = "text/javascript"; 654 | if ( jQuery.support.scriptEval ) 655 | script.appendChild( document.createTextNode( data ) ); 656 | else 657 | script.text = data; 658 | 659 | // Use insertBefore instead of appendChild to circumvent an IE6 bug. 660 | // This arises when a base node is used (#2709). 661 | head.insertBefore( script, head.firstChild ); 662 | head.removeChild( script ); 663 | } 664 | }, 665 | 666 | nodeName: function( elem, name ) { 667 | return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); 668 | }, 669 | 670 | // args is for internal usage only 671 | each: function( object, callback, args ) { 672 | var name, i = 0, length = object.length; 673 | 674 | if ( args ) { 675 | if ( length === undefined ) { 676 | for ( name in object ) 677 | if ( callback.apply( object[ name ], args ) === false ) 678 | break; 679 | } else 680 | for ( ; i < length; ) 681 | if ( callback.apply( object[ i++ ], args ) === false ) 682 | break; 683 | 684 | // A special, fast, case for the most common use of each 685 | } else { 686 | if ( length === undefined ) { 687 | for ( name in object ) 688 | if ( callback.call( object[ name ], name, object[ name ] ) === false ) 689 | break; 690 | } else 691 | for ( var value = object[0]; 692 | i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} 693 | } 694 | 695 | return object; 696 | }, 697 | 698 | prop: function( elem, value, type, i, name ) { 699 | // Handle executable functions 700 | if ( jQuery.isFunction( value ) ) 701 | value = value.call( elem, i ); 702 | 703 | // Handle passing in a number to a CSS property 704 | return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ? 705 | value + "px" : 706 | value; 707 | }, 708 | 709 | className: { 710 | // internal only, use addClass("class") 711 | add: function( elem, classNames ) { 712 | jQuery.each((classNames || "").split(/\s+/), function(i, className){ 713 | if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) 714 | elem.className += (elem.className ? " " : "") + className; 715 | }); 716 | }, 717 | 718 | // internal only, use removeClass("class") 719 | remove: function( elem, classNames ) { 720 | if (elem.nodeType == 1) 721 | elem.className = classNames !== undefined ? 722 | jQuery.grep(elem.className.split(/\s+/), function(className){ 723 | return !jQuery.className.has( classNames, className ); 724 | }).join(" ") : 725 | ""; 726 | }, 727 | 728 | // internal only, use hasClass("class") 729 | has: function( elem, className ) { 730 | return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; 731 | } 732 | }, 733 | 734 | // A method for quickly swapping in/out CSS properties to get correct calculations 735 | swap: function( elem, options, callback ) { 736 | var old = {}; 737 | // Remember the old values, and insert the new ones 738 | for ( var name in options ) { 739 | old[ name ] = elem.style[ name ]; 740 | elem.style[ name ] = options[ name ]; 741 | } 742 | 743 | callback.call( elem ); 744 | 745 | // Revert the old values 746 | for ( var name in options ) 747 | elem.style[ name ] = old[ name ]; 748 | }, 749 | 750 | css: function( elem, name, force, extra ) { 751 | if ( name == "width" || name == "height" ) { 752 | var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; 753 | 754 | function getWH() { 755 | val = name == "width" ? elem.offsetWidth : elem.offsetHeight; 756 | 757 | if ( extra === "border" ) 758 | return; 759 | 760 | jQuery.each( which, function() { 761 | if ( !extra ) 762 | val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; 763 | if ( extra === "margin" ) 764 | val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0; 765 | else 766 | val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; 767 | }); 768 | } 769 | 770 | if ( elem.offsetWidth !== 0 ) 771 | getWH(); 772 | else 773 | jQuery.swap( elem, props, getWH ); 774 | 775 | return Math.max(0, Math.round(val)); 776 | } 777 | 778 | return jQuery.curCSS( elem, name, force ); 779 | }, 780 | 781 | curCSS: function( elem, name, force ) { 782 | var ret, style = elem.style; 783 | 784 | // We need to handle opacity special in IE 785 | if ( name == "opacity" && !jQuery.support.opacity ) { 786 | ret = jQuery.attr( style, "opacity" ); 787 | 788 | return ret == "" ? 789 | "1" : 790 | ret; 791 | } 792 | 793 | // Make sure we're using the right name for getting the float value 794 | if ( name.match( /float/i ) ) 795 | name = styleFloat; 796 | 797 | if ( !force && style && style[ name ] ) 798 | ret = style[ name ]; 799 | 800 | else if ( defaultView.getComputedStyle ) { 801 | 802 | // Only "float" is needed here 803 | if ( name.match( /float/i ) ) 804 | name = "float"; 805 | 806 | name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); 807 | 808 | var computedStyle = defaultView.getComputedStyle( elem, null ); 809 | 810 | if ( computedStyle ) 811 | ret = computedStyle.getPropertyValue( name ); 812 | 813 | // We should always get a number back from opacity 814 | if ( name == "opacity" && ret == "" ) 815 | ret = "1"; 816 | 817 | } else if ( elem.currentStyle ) { 818 | var camelCase = name.replace(/\-(\w)/g, function(all, letter){ 819 | return letter.toUpperCase(); 820 | }); 821 | 822 | ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; 823 | 824 | // From the awesome hack by Dean Edwards 825 | // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 826 | 827 | // If we're not dealing with a regular pixel number 828 | // but a number that has a weird ending, we need to convert it to pixels 829 | if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { 830 | // Remember the original values 831 | var left = style.left, rsLeft = elem.runtimeStyle.left; 832 | 833 | // Put in the new values to get a computed value out 834 | elem.runtimeStyle.left = elem.currentStyle.left; 835 | style.left = ret || 0; 836 | ret = style.pixelLeft + "px"; 837 | 838 | // Revert the changed values 839 | style.left = left; 840 | elem.runtimeStyle.left = rsLeft; 841 | } 842 | } 843 | 844 | return ret; 845 | }, 846 | 847 | clean: function( elems, context, fragment ) { 848 | context = context || document; 849 | 850 | // !context.createElement fails in IE with an error but returns typeof 'object' 851 | if ( typeof context.createElement === "undefined" ) 852 | context = context.ownerDocument || context[0] && context[0].ownerDocument || document; 853 | 854 | // If a single string is passed in and it's a single tag 855 | // just do a createElement and skip the rest 856 | if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) { 857 | var match = /^<(\w+)\s*\/?>$/.exec(elems[0]); 858 | if ( match ) 859 | return [ context.createElement( match[1] ) ]; 860 | } 861 | 862 | var ret = [], scripts = [], div = context.createElement("div"); 863 | 864 | jQuery.each(elems, function(i, elem){ 865 | if ( typeof elem === "number" ) 866 | elem += ''; 867 | 868 | if ( !elem ) 869 | return; 870 | 871 | // Convert html string into DOM nodes 872 | if ( typeof elem === "string" ) { 873 | // Fix "XHTML"-style tags in all browsers 874 | elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ 875 | return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? 876 | all : 877 | front + ">"; 878 | }); 879 | 880 | // Trim whitespace, otherwise indexOf won't work as expected 881 | var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase(); 882 | 883 | var wrap = 884 | // option or optgroup 885 | !tags.indexOf("", "" ] || 887 | 888 | !tags.indexOf("", "" ] || 890 | 891 | tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && 892 | [ 1, "", "
" ] || 893 | 894 | !tags.indexOf("", "" ] || 896 | 897 | // matched above 898 | (!tags.indexOf("", "" ] || 900 | 901 | !tags.indexOf("", "" ] || 903 | 904 | // IE can't serialize and