mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
@@ -103,11 +103,9 @@ def extra_static_content(user):
|
||||
return []
|
||||
|
||||
tpl = Template("""<script type="text/javascript">
|
||||
function check_nb_requests() {
|
||||
$.ajax({
|
||||
url: "{{ url }}",
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
$(document).ready(function() {
|
||||
var poller = new Poller("{{ url }}", {
|
||||
success_cb: function(data) {
|
||||
var $link = $("#nbrequests");
|
||||
if (data.requests > 0) {
|
||||
$link.html(data.requests + " " + gettext("pending requests"));
|
||||
@@ -117,10 +115,6 @@ function check_nb_requests() {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
setInterval(check_nb_requests, 2000);
|
||||
});
|
||||
</script>""")
|
||||
url = reverse("modoboa.extensions.amavis_quarantine.views.nbrequests")
|
||||
|
||||
@@ -303,4 +303,4 @@ def process(request):
|
||||
@user_passes_test(lambda u: u.group != 'SimpleUsers')
|
||||
def nbrequests(request):
|
||||
nbrequests = get_nb_requests(request.user)
|
||||
return ajax_simple_response(dict(requests=nbrequests))
|
||||
return ajax_simple_response(dict(status="ok", requests=nbrequests))
|
||||
|
||||
@@ -150,23 +150,26 @@ class FilterForm(forms.Form):
|
||||
def _build_fileinto_field(self, request, value):
|
||||
self._build_action_field(request, "fileinto", value)
|
||||
|
||||
def __build_folders_list(self, folders):
|
||||
def __build_folders_list(self, folders, user, imapc, parentmb=None):
|
||||
ret = []
|
||||
for fd in folders:
|
||||
value = fd["path"] if fd.has_key("path") else fd["name"]
|
||||
ret += [(value, fd["name"])]
|
||||
if fd.has_key("sub") and len(fd["sub"]):
|
||||
ret += self.__build_folders_list(fd["sub"])
|
||||
if parentmb:
|
||||
ret += [(value, fd["name"].replace("%s." % parentmb, ""))]
|
||||
else:
|
||||
ret += [(value, fd["name"])]
|
||||
if fd.has_key("sub"):
|
||||
submboxes = imapc.getmboxes(user, value, unseen_messages=False)
|
||||
ret += self.__build_folders_list(submboxes, user, imapc, value)
|
||||
return ret
|
||||
|
||||
def userfolders(self, request):
|
||||
from modoboa.extensions.webmail.lib import IMAPconnector
|
||||
from modoboa.extensions.webmail.imaputils import get_imapconnector
|
||||
|
||||
mbc = IMAPconnector(user=request.user.username,
|
||||
password=request.session["password"])
|
||||
ret = mbc.getfolders(request.user, unseen_messages=False)
|
||||
mbc = get_imapconnector(request)
|
||||
ret = mbc.getmboxes(request.user, unseen_messages=False)
|
||||
|
||||
folders = self.__build_folders_list(ret)
|
||||
folders = self.__build_folders_list(ret, request.user, mbc)
|
||||
return folders
|
||||
|
||||
def tofilter(self):
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
|
||||
var DynamicCondition = new Class({
|
||||
|
||||
Extends: DynamicTextInput,
|
||||
|
||||
options: {
|
||||
defaultcond: "Subject",
|
||||
templates: []
|
||||
},
|
||||
|
||||
loadoperators: function(container, tpl) {
|
||||
container.empty();
|
||||
for (var ocpt = 0; ocpt < tpl["operators"].length; ocpt++) {
|
||||
var opt = new Element("option", {
|
||||
value: tpl["operators"][ocpt][0],
|
||||
html: tpl["operators"][ocpt][1]
|
||||
});
|
||||
opt.inject(container);
|
||||
}
|
||||
},
|
||||
|
||||
onTargetChange: function(event) {
|
||||
var parts = event.target.get("name").split("_");
|
||||
var container = $("id_cond_operator_" + parts[2]);
|
||||
|
||||
for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
|
||||
var tpl = this.options.templates[cpt];
|
||||
if (tpl["name"] != event.target.get("value")) {
|
||||
continue;
|
||||
}
|
||||
this.loadoperators(container, tpl);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
createfield: function() {
|
||||
var div = new Element("div", {"class": "item"});
|
||||
var condtarget = new Element("select", {
|
||||
id: "id_cond_target_" + this.nextid,
|
||||
name: "cond_target_" + this.nextid
|
||||
});
|
||||
var condoperator = new Element("select", {
|
||||
id: "id_cond_operator_" + this.nextid,
|
||||
name: "cond_operator_" + this.nextid
|
||||
});
|
||||
var condvalue = new Element("input", {
|
||||
id: "id_cond_value_" + this.nextid,
|
||||
name: "cond_value_" + this.nextid,
|
||||
type: "text"
|
||||
});
|
||||
|
||||
for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
|
||||
var tpl = this.options.templates[cpt];
|
||||
var opt = new Element("option", {
|
||||
value: tpl["name"],
|
||||
html: tpl["label"]
|
||||
});
|
||||
opt.inject(condtarget);
|
||||
|
||||
if (tpl["name"] != this.options.defaultcond) {
|
||||
continue;
|
||||
}
|
||||
this.loadoperators(condoperator, tpl);
|
||||
}
|
||||
condtarget.inject(div);
|
||||
condtarget.addEvent("change", this.onTargetChange.bind(this));
|
||||
condoperator.inject(div);
|
||||
condvalue.inject(div);
|
||||
return [div, condvalue];
|
||||
},
|
||||
|
||||
removeelement: function(event) {
|
||||
var id = (event.target.get("id").split("_"))[1];
|
||||
|
||||
event.stop();
|
||||
$("id_cond_target_" + id).destroy();
|
||||
$("id_cond_operator_" + id).destroy();
|
||||
$("id_cond_value_" + id).destroy();
|
||||
event.target.destroy();
|
||||
this.fields_cnt--;
|
||||
}
|
||||
});
|
||||
|
||||
var DynamicAction = new Class({
|
||||
Extends: DynamicTextInput,
|
||||
|
||||
options: {
|
||||
defaultaction: "fileinto",
|
||||
templates: [],
|
||||
ufolders_url: null
|
||||
},
|
||||
|
||||
initialize: function(targets, options) {
|
||||
this.parent(targets, options);
|
||||
$("id_action_arg_0_0").getElements("option").each(function(item) {
|
||||
var cpt = 1;
|
||||
var start = 0;
|
||||
var value = item.get("value");
|
||||
|
||||
while (true) {
|
||||
var id = value.indexOf(".", start);
|
||||
if (id == -1) {
|
||||
break;
|
||||
}
|
||||
item.setStyle("margin-left", (cpt * 20) + "px");
|
||||
cpt++;
|
||||
start = id + 1;
|
||||
}
|
||||
});
|
||||
this.fdoptions = $("id_action_arg_0_0").getElements("option");
|
||||
},
|
||||
|
||||
loadargs: function(id, tpl) {
|
||||
var res = new Elements();
|
||||
|
||||
for (var acpt = 0; acpt < tpl["args"].length; acpt++) {
|
||||
var argname = "action_arg_" + id + "_" + acpt;
|
||||
var child = null;
|
||||
|
||||
if (tpl["args"][acpt]["type"] == "list") {
|
||||
child = new Element("select", {id: "id_" + argname, name: argname});
|
||||
if ($defined(tpl["args"][acpt]["vloader"])) {
|
||||
this[tpl["args"][acpt]["vloader"]](child);
|
||||
}
|
||||
} else if (tpl["args"][acpt]["type"] == "string") {
|
||||
child = new Element("input", {type: "text",
|
||||
id: "id_" + argname, name: argname});
|
||||
}
|
||||
res.push(child);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
|
||||
deletePrevArgs: function(id) {
|
||||
for (var cpt = 0; true; cpt++) {
|
||||
var arg = $("id_action_arg_" + id + "_" + cpt);
|
||||
if (!$defined(arg)) {
|
||||
break;
|
||||
}
|
||||
arg.destroy();
|
||||
}
|
||||
},
|
||||
|
||||
onNameChange: function(event) {
|
||||
var parts = event.target.get("name").split("_");
|
||||
var container = event.target.getParent();
|
||||
|
||||
event.stop();
|
||||
this.deletePrevArgs(parts[2]);
|
||||
for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
|
||||
var tpl = this.options.templates[cpt];
|
||||
if (tpl["name"] != event.target.get("value")) {
|
||||
continue;
|
||||
}
|
||||
var args = this.loadargs(parts[2], tpl);
|
||||
args.inject(container);
|
||||
}
|
||||
},
|
||||
|
||||
createfield: function() {
|
||||
var div = new Element("div", {"class": "item"});
|
||||
var actionname = new Element("select", {
|
||||
id: "id_action_name_" + this.nextid,
|
||||
name: "action_name_" + this.nextid
|
||||
});
|
||||
var args = null;
|
||||
|
||||
for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
|
||||
var tpl = this.options.templates[cpt];
|
||||
var opt = new Element("option", {
|
||||
value: tpl["name"],
|
||||
html: tpl["label"]
|
||||
});
|
||||
opt.inject(actionname);
|
||||
|
||||
if (tpl["name"] != this.options.defaultaction) {
|
||||
continue;
|
||||
}
|
||||
args = this.loadargs(this.nextid, tpl);
|
||||
}
|
||||
actionname.inject(div);
|
||||
actionname.addEvent("change", this.onNameChange.bind(this));
|
||||
args.inject(div);
|
||||
return [div, actionname];
|
||||
},
|
||||
|
||||
removeelement: function(event) {
|
||||
var id = (event.target.get("id").split("_"))[1];
|
||||
|
||||
event.stop();
|
||||
$("id_action_name_" + id).destroy();
|
||||
for (var cpt = 0; true; cpt++) {
|
||||
var arg = $("id_action_arg_" + id + "_" + cpt);
|
||||
if (!$defined(arg)) {
|
||||
break;
|
||||
}
|
||||
arg.destroy();
|
||||
}
|
||||
event.target.destroy();
|
||||
this.fields_cnt--;
|
||||
},
|
||||
|
||||
userfolders: function(container) {
|
||||
this.fdoptions.each(function(item) {
|
||||
item.clone().inject(container);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -54,7 +54,7 @@
|
||||
return $('<a />', {
|
||||
href: '#',
|
||||
id: 'condition_' + this.nextid + '_rmbtn',
|
||||
html: '<i class="icon-remove"></i>'
|
||||
html: '<i class="icon-trash"></i>'
|
||||
}).click($.proxy(this.removefields, this));
|
||||
},
|
||||
|
||||
@@ -250,7 +250,7 @@
|
||||
return $('<a />', {
|
||||
href: '#',
|
||||
id: 'action_' + this.nextid + '_rmbtn',
|
||||
html: '<i class="icon-remove"></i>'
|
||||
html: '<i class="icon-trash"></i>'
|
||||
}).click($.proxy(this.removefields, this));
|
||||
},
|
||||
|
||||
@@ -322,7 +322,6 @@
|
||||
|
||||
$.fn.dynaction.defaults = {
|
||||
defaultaction: "fileinto",
|
||||
templates: [],
|
||||
ufolders_url: null
|
||||
templates: []
|
||||
};
|
||||
})(jQuery);
|
||||
@@ -22,8 +22,7 @@ function filterform_cb() {
|
||||
templates: cond_templates
|
||||
}).data('dyncondition');
|
||||
var $da = $("#action_0").dynaction({
|
||||
templates: action_templates,
|
||||
ufolders_url: "{% url modoboa.extensions.sievefilters.views.get_user_folders %}"
|
||||
templates: action_templates
|
||||
}).data('dynaction');
|
||||
|
||||
$("input[name=match_type]").click(function(e) {
|
||||
|
||||
@@ -53,7 +53,7 @@ def fset_menu(mode, setname):
|
||||
"url" : reverse("modoboa.extensions.sievefilters.views.activate_filters_set",
|
||||
args=[setname])},
|
||||
{"name" : "removefs",
|
||||
"img" : "icon-remove",
|
||||
"img" : "icon-trash",
|
||||
"label" : _("Remove filters set"),
|
||||
"url" : reverse("modoboa.extensions.sievefilters.views.remove_filters_set",
|
||||
args=[setname])},
|
||||
@@ -81,7 +81,7 @@ def filter_actions(setname, f, position, islast):
|
||||
{"name" : "removefilter",
|
||||
"url" : reverse("modoboa.extensions.sievefilters.views.removefilter",
|
||||
args=[setname, f["name"]]),
|
||||
"img" : "icon-remove",
|
||||
"img" : "icon-trash",
|
||||
"title" : _("Remove this filter")}
|
||||
]
|
||||
if position != 1:
|
||||
|
||||
@@ -8,7 +8,6 @@ urlpatterns = patterns('modoboa.extensions.sievefilters.views',
|
||||
(r'^activatefs/(?P<name>.+)/$', 'activate_filters_set'),
|
||||
(r'^downloadfs/(?P<name>.+)/$', 'download_filters_set'),
|
||||
(r'^templates/(?P<ftype>\w+)/$', 'get_templates'),
|
||||
(r'^userfolders/$', 'get_user_folders'),
|
||||
(r'^(?P<setname>.+)/newfilter/$', 'newfilter'),
|
||||
(r'^(?P<setname>.+)/editfilter/(?P<fname>.+)/$', 'editfilter'),
|
||||
(r'^(?P<setname>.+)/removefilter/(?P<fname>.+)/$', 'removefilter'),
|
||||
|
||||
@@ -48,16 +48,6 @@ def get_templates(request, ftype):
|
||||
return ajax_simple_response(FilterForm([], [], request).cond_templates)
|
||||
return ajax_simple_response(FilterForm([], [], request).action_templates)
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def get_user_folders(request):
|
||||
from modoboa.extensions.webmail.lib import IMAPconnector
|
||||
|
||||
mbc = IMAPconnector(user=request.user.username,
|
||||
password=request.session["password"])
|
||||
ret = mbc.getfolders(request.user, unseen_messages=False)
|
||||
return ajax_simple_response(ret)
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def getfs(request, name):
|
||||
|
||||
@@ -149,7 +149,7 @@ class IMAPconnector(object):
|
||||
:param name: the command's name
|
||||
:return: the command's result
|
||||
"""
|
||||
if name in ['FETCH', 'SORT', 'STORE', 'COPY']:
|
||||
if name in ['FETCH', 'SORT', 'STORE', 'COPY', 'SEARCH']:
|
||||
try:
|
||||
typ, data = self.m.uid(name, *args)
|
||||
except imaplib.IMAP4.error, e:
|
||||
@@ -211,12 +211,15 @@ class IMAPconnector(object):
|
||||
"""
|
||||
if self.m is not None:
|
||||
try:
|
||||
self.m.select()
|
||||
self._cmd("CHECK")
|
||||
except ImapError, e:
|
||||
if hasattr(self, "current_mailbox"):
|
||||
del self.current_mailbox
|
||||
else:
|
||||
return
|
||||
except imaplib.IMAP4.error, error:
|
||||
print error
|
||||
print self.login(user, password)
|
||||
|
||||
status, msg = self.login(user, password)
|
||||
if not status:
|
||||
raise ImapError(msg)
|
||||
|
||||
def login(self, user, passwd):
|
||||
import socket
|
||||
@@ -263,14 +266,18 @@ class IMAPconnector(object):
|
||||
else:
|
||||
criterion = "REVERSE DATE"
|
||||
folder = kwargs.has_key("folder") and kwargs["folder"] or None
|
||||
self.select_mailbox(folder, False)
|
||||
|
||||
# FIXME: pourquoi suis je obligé de faire un SELECT ici? un
|
||||
# EXAMINE plante mais je pense que c'est du à une mauvaise
|
||||
# lecture des réponses de ma part...
|
||||
self.select_mailbox(folder, readonly=False)
|
||||
data = self._cmd("SORT", "(%s)" % criterion, "UTF-8", "(NOT DELETED)",
|
||||
*self.criterions)
|
||||
self.messages = data[0].split()
|
||||
self.getquota(folder)
|
||||
return len(self.messages)
|
||||
|
||||
def select_mailbox(self, name, readonly=True):
|
||||
def select_mailbox(self, name, readonly=True, force=False):
|
||||
"""Issue a SELECT/EXAMINE command to the server
|
||||
|
||||
The given name is first 'imap-utf7' encoded.
|
||||
@@ -278,11 +285,16 @@ class IMAPconnector(object):
|
||||
:param name: mailbox's name
|
||||
:param readonly:
|
||||
"""
|
||||
if hasattr(self, "current_mailbox"):
|
||||
if self.current_mailbox == name and not force:
|
||||
return
|
||||
self.current_mailbox = name
|
||||
name = name.encode("imap4-utf-7")
|
||||
if readonly:
|
||||
self._cmd("EXAMINE", name)
|
||||
else:
|
||||
self._cmd("SELECT", name)
|
||||
self.m.state = "SELECTED"
|
||||
|
||||
def unseen_messages(self, mailbox):
|
||||
"""Return the number of unseen messages
|
||||
@@ -290,21 +302,18 @@ class IMAPconnector(object):
|
||||
:param mailbox: the mailbox's name
|
||||
:return: an integer
|
||||
"""
|
||||
# self.select_mailbox(mailbox)
|
||||
# data = self._cmd("SEARCH", "UTF-8", "(NOT DELETED UNSEEN)")
|
||||
# return len(data[0].split())
|
||||
data = self._cmd("STATUS", mailbox.encode("imap4-utf-7"), "(UNSEEN)")
|
||||
m = self.unseen_pattern.match(data[0])
|
||||
if m is None:
|
||||
return 0
|
||||
return int(m.group(1))
|
||||
|
||||
def _encodefolder(self, folder):
|
||||
def _encode_mbox_name(self, folder):
|
||||
if not folder:
|
||||
return "INBOX"
|
||||
return folder.encode("imap4-utf-7")
|
||||
|
||||
def _parse_folder_name(self, descr, prefix, delimiter, parts):
|
||||
def _parse_mailbox_name(self, descr, prefix, delimiter, parts):
|
||||
if not len(parts):
|
||||
return False
|
||||
path = "%s%s%s" % (prefix, delimiter, parts[0])
|
||||
@@ -316,11 +325,11 @@ class IMAPconnector(object):
|
||||
if sdescr is None:
|
||||
sdescr = {"name" : parts[0], "path" : path, "sub" : []}
|
||||
descr += [sdescr]
|
||||
if self._parse_folder_name(sdescr["sub"], path, delimiter, parts[1:]):
|
||||
if self._parse_mailbox_name(sdescr["sub"], path, delimiter, parts[1:]):
|
||||
sdescr["class"] = "subfolders"
|
||||
return True
|
||||
|
||||
def _listfolders_simple(self, topmailbox='INBOX', md_mailboxes=[]):
|
||||
def _listmboxes_simple(self, topmailbox='INBOX', md_mailboxes=[]):
|
||||
(status, data) = self.m.list()
|
||||
result = []
|
||||
for mb in data:
|
||||
@@ -331,8 +340,8 @@ class IMAPconnector(object):
|
||||
if not descr.has_key("path"):
|
||||
descr["path"] = parts[0]
|
||||
descr["sub"] = []
|
||||
if self._parse_folder_name(descr["sub"], parts[0], delimiter,
|
||||
parts[1:]):
|
||||
if self._parse_mailbox_name(descr["sub"], parts[0], delimiter,
|
||||
parts[1:]):
|
||||
descr["class"] = "subfolders"
|
||||
continue
|
||||
present = False
|
||||
@@ -347,31 +356,35 @@ class IMAPconnector(object):
|
||||
return sorted(result, key=itemgetter("name"))
|
||||
|
||||
@capability('LIST-EXTENDED', '_listfolders_simple')
|
||||
def _listfolders(self, topmailbox='', md_mailboxes=[]):
|
||||
def _listmboxes(self, topmailbox='', mailboxes=[]):
|
||||
pattern = ("%s.%%" % topmailbox.encode("imap4-utf-7")) if len(topmailbox) else "%"
|
||||
resp = self._cmd("LIST", "", pattern, "RETURN", "(CHILDREN)")
|
||||
result = []
|
||||
newmboxes = []
|
||||
for mb in resp:
|
||||
flags, delimiter, name, childinfo = \
|
||||
self.listextended_response_pattern.match(mb).groups()
|
||||
flags = flags.split(' ')
|
||||
name = name.decode("imap4-utf-7")
|
||||
present = False
|
||||
for mdm in md_mailboxes:
|
||||
pos = -1
|
||||
for idx, mdm in enumerate(mailboxes):
|
||||
if mdm["name"] == name:
|
||||
present = True
|
||||
pos = idx
|
||||
break
|
||||
if present:
|
||||
continue
|
||||
descr = dict(name=name)
|
||||
if pos == -1:
|
||||
descr = dict(name=name)
|
||||
newmboxes += [descr]
|
||||
else:
|
||||
descr = mailboxes[idx]
|
||||
|
||||
if r'\Marked' in flags or not r'\UnMarked' in flags:
|
||||
descr["send_status"] = True
|
||||
if r'\HasChildren' in flags:
|
||||
descr["path"] = name
|
||||
descr["sub"] = []
|
||||
result += [descr]
|
||||
from operator import itemgetter
|
||||
return sorted(result, key=itemgetter("name"))
|
||||
mailboxes += sorted(newmboxes, key=itemgetter("name"))
|
||||
|
||||
def getfolders(self, user, topmailbox='', unseen_messages=True):
|
||||
def getmboxes(self, user, topmailbox='', unseen_messages=True):
|
||||
if len(topmailbox):
|
||||
md_mailboxes = []
|
||||
else:
|
||||
@@ -382,14 +395,18 @@ class IMAPconnector(object):
|
||||
{"name" : parameters.get_user(user, "SENT_FOLDER")},
|
||||
{"name" : parameters.get_user(user, "TRASH_FOLDER"),
|
||||
"class" : "icon-trash"}]
|
||||
md_mailboxes += self._listfolders(topmailbox, md_mailboxes)
|
||||
self._listmboxes(topmailbox, md_mailboxes)
|
||||
|
||||
if unseen_messages:
|
||||
for fd in md_mailboxes:
|
||||
key = fd.has_key("path") and "path" or "name"
|
||||
count = self.unseen_messages(fd[key])
|
||||
for mb in md_mailboxes:
|
||||
if not mb.has_key("send_status"):
|
||||
continue
|
||||
del mb["send_status"]
|
||||
key = mb.has_key("path") and "path" or "name"
|
||||
count = self.unseen_messages(mb[key])
|
||||
if count == 0:
|
||||
continue
|
||||
fd["unseen"] = count
|
||||
mb["unseen"] = count
|
||||
return md_mailboxes
|
||||
|
||||
def _add_flag(self, mbox, msgset, flag):
|
||||
@@ -432,20 +449,18 @@ class IMAPconnector(object):
|
||||
"""
|
||||
self.select_mailbox(oldmailbox, False)
|
||||
self._cmd("COPY", msgset, newmailbox.encode("imap4-utf-7"))
|
||||
self._cmd("STORE", msgset, "+FLAGS", r'(\Deleted)')
|
||||
self._cmd("STORE", msgset, "+FLAGS", r'(\Deleted \Seen)')
|
||||
|
||||
def push_mail(self, folder, msg):
|
||||
now = imaplib.Time2Internaldate(time.time())
|
||||
self.m.append(self._encodefolder(folder), r'(\Seen)', now, str(msg))
|
||||
self.m.append(self._encode_mbox_name(folder), r'(\Seen)', now, str(msg))
|
||||
|
||||
def empty(self, mbox):
|
||||
self.select_mailbox(mbox, False)
|
||||
resp = self._cmd("SEARCH", "ALL")
|
||||
print resp
|
||||
|
||||
# for num in data[0].split():
|
||||
# self.m.store(num, "+FLAGS", r'(\Deleted)')
|
||||
# self.m.expunge()
|
||||
seq = ",".join(resp[0].split())
|
||||
self._cmd("STORE", seq, "+FLAGS", r'(\Deleted)')
|
||||
self._cmd("EXPUNGE")
|
||||
|
||||
def compact(self, mbox):
|
||||
"""Compact a specific mailbox
|
||||
@@ -460,20 +475,20 @@ class IMAPconnector(object):
|
||||
def create_folder(self, name, parent=None):
|
||||
if parent is not None:
|
||||
name = "%s.%s" % (parent, name)
|
||||
typ, data = self.m.create(self._encodefolder(name))
|
||||
typ, data = self.m.create(self._encode_mbox_name(name))
|
||||
if typ == "NO":
|
||||
raise WebmailError(data[0])
|
||||
return True
|
||||
|
||||
def rename_folder(self, oldname, newname):
|
||||
typ, data = self.m.rename(self._encodefolder(oldname),
|
||||
self._encodefolder(newname))
|
||||
typ, data = self.m.rename(self._encode_mbox_name(oldname),
|
||||
self._encode_mbox_name(newname))
|
||||
if typ == "NO":
|
||||
raise WebmailError(data[0], ajax=True)
|
||||
return True
|
||||
|
||||
def delete_folder(self, name):
|
||||
typ, data = self.m.delete(self._encodefolder(name))
|
||||
typ, data = self.m.delete(self._encode_mbox_name(name))
|
||||
if typ == "NO":
|
||||
raise WebmailError(data[0])
|
||||
return True
|
||||
@@ -483,7 +498,7 @@ class IMAPconnector(object):
|
||||
self.quota_limit = self.quota_actual = None
|
||||
return
|
||||
|
||||
data = self._cmd("GETQUOTAROOT", self._encodefolder(mailbox),
|
||||
data = self._cmd("GETQUOTAROOT", self._encode_mbox_name(mailbox),
|
||||
responses=["QUOTAROOT", "QUOTA"])
|
||||
if data is None:
|
||||
self.quota_limit = self.quota_actual = None
|
||||
@@ -526,7 +541,7 @@ class IMAPconnector(object):
|
||||
msg = email.message_from_string(data[int(uid)]['BODY[HEADER.FIELDS (DATE FROM TO CC SUBJECT)]'])
|
||||
msg['imapid'] = uid
|
||||
if not r'\Seen' in data[int(uid)]['FLAGS']:
|
||||
msg['class'] = 'unseen'
|
||||
msg['style'] = 'unseen'
|
||||
if r'\Answered' in data[int(uid)]['FLAGS']:
|
||||
msg['img_flags'] = static_url('pics/answered.png')
|
||||
if r'$Forwarded' in data[int(uid)]['FLAGS']:
|
||||
@@ -551,6 +566,7 @@ class IMAPconnector(object):
|
||||
:param readonly:
|
||||
:param extraheaders:
|
||||
"""
|
||||
print mbox
|
||||
self.select_mailbox(mbox, readonly)
|
||||
if headers is None:
|
||||
headers = ['DATE', 'FROM', 'TO', 'CC', 'SUBJECT']
|
||||
@@ -562,6 +578,10 @@ class IMAPconnector(object):
|
||||
return data[int(mailid)]
|
||||
|
||||
def get_imapconnector(request):
|
||||
"""Simple shortcut to create a connector
|
||||
|
||||
:param request: a ``Request`` object
|
||||
"""
|
||||
imapc = IMAPconnector(user=request.user.username,
|
||||
password=request.session["password"])
|
||||
return imapc
|
||||
|
||||
@@ -143,16 +143,18 @@ def print_folders(folders, selected=None, withunseen=False, selectonly=False):
|
||||
result += "<div class='clickbox %s'></div>" % div_state
|
||||
|
||||
cssclass = "block"
|
||||
extra_attrs = ""
|
||||
if withunseen and fd.has_key("unseen"):
|
||||
label += " (%d)" % fd["unseen"]
|
||||
cssclass += " unseen"
|
||||
extra_attrs = ' data-toggle="%d"' % fd["unseen"]
|
||||
iclass = fd["class"] if fd.has_key("class") else "icon-folder-close"
|
||||
result += """<a href='%s' class='%s' name='%s'>
|
||||
result += """<a href='%s' class='%s' name='%s'%s>
|
||||
<i class="%s"></i>
|
||||
%s
|
||||
</a>
|
||||
""" % (fd.has_key("path") and fd["path"] or fd["name"], cssclass,
|
||||
'selectfolder' if selectonly else 'loadfolder', iclass, label)
|
||||
'selectfolder' if selectonly else 'loadfolder', extra_attrs, iclass, label)
|
||||
if fd.has_key("sub") and len(fd["sub"]):
|
||||
result += "<ul name='%s' class='%s'>" % (fd["path"], ul_state) \
|
||||
+ print_folders(fd["sub"], selected, withunseen, selectonly) + "</ul>\n"
|
||||
|
||||
@@ -11,6 +11,7 @@ urlpatterns = patterns(
|
||||
(r'^$', "newindex"),
|
||||
(r'^submailboxes', "submailboxes"),
|
||||
(r'^getmailcontent', 'getmailcontent'),
|
||||
(r'^unseenmsgs', 'check_unseen_messages'),
|
||||
|
||||
# (r'^compose/$', "compose"),
|
||||
(r'^delete/$', 'delete'),
|
||||
|
||||
+67
-39
@@ -63,7 +63,7 @@ def __render_common_components(request, folder_name, lst=None, content=None, men
|
||||
"folders" : _render_to_string(request, "webmail/folders.html", {
|
||||
"titlebar" : True,
|
||||
"selected" : request.session["folder"],
|
||||
"folders" : mbc.getfolders(request.user),
|
||||
"folders" : mbc.getmboxes(request.user),
|
||||
"withunseen" : True
|
||||
}),
|
||||
"menu" : menu,
|
||||
@@ -125,38 +125,50 @@ def index(request):
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def getattachment(request, folder, mail_id):
|
||||
if request.GET.has_key("partnumber"):
|
||||
headers = {"Content-Type" : "text/plain",
|
||||
"Content-Transfer-Encoding" : None}
|
||||
icon = IMAPconnector(user=request.user.username,
|
||||
password=request.session["password"])
|
||||
part = icon.fetchpart(mail_id, folder, request.GET["partnumber"])
|
||||
if part is not None:
|
||||
if part.get_content_maintype() == "message":
|
||||
payload = part.get_payload(0)
|
||||
else:
|
||||
payload = part.get_payload(decode=True)
|
||||
resp = HttpResponse(payload)
|
||||
for hdr, default in headers.iteritems():
|
||||
if not part.has_key(hdr):
|
||||
if default is not None:
|
||||
resp[hdr] = default
|
||||
else:
|
||||
continue
|
||||
resp[hdr] = re.sub("\s", "", part[hdr])
|
||||
# I would add this part into the previous loop if I was
|
||||
# able to use functions as default values... but I'm a bit
|
||||
# lazy :p
|
||||
if part.has_key("Content-Disposition"):
|
||||
resp["Content-Disposition"] = \
|
||||
re.sub("\s", "", part["Content-Disposition"])
|
||||
else:
|
||||
resp["Content-Disposition"] = \
|
||||
"attachment; filename=%s" % request.GET["fname"]
|
||||
resp["Content-Length"] = len(payload)
|
||||
return resp
|
||||
raise Http404
|
||||
"""Fetch a message attachment
|
||||
|
||||
Problème lié aux optimisations apportées : la méthode fetchpart du
|
||||
client IMAP ne renvoie que le payload. Pour obtenir les headers,
|
||||
deux solutions :
|
||||
|
||||
* Conserver le bodystructure parsé précédemment (nécessite du caching)
|
||||
|
||||
* Reparser le bodystructure :p
|
||||
"""
|
||||
pnum = request.GET.get("partnumber", None)
|
||||
if not pnum:
|
||||
raise Http404
|
||||
|
||||
headers = {"Content-Type" : "text/plain",
|
||||
"Content-Transfer-Encoding" : None}
|
||||
imapc = get_imapconnector(request)
|
||||
part = imapc.fetchpart(mail_id, folder, pnum)
|
||||
if part is None:
|
||||
raise Http404
|
||||
if part.get_content_maintype() == "message":
|
||||
payload = part.get_payload(0)
|
||||
else:
|
||||
payload = part.get_payload(decode=True)
|
||||
resp = HttpResponse(payload)
|
||||
for hdr, default in headers.iteritems():
|
||||
if not part.has_key(hdr):
|
||||
if default is not None:
|
||||
resp[hdr] = default
|
||||
else:
|
||||
continue
|
||||
resp[hdr] = re.sub("\s", "", part[hdr])
|
||||
# I would add this part into the previous loop if I was
|
||||
# able to use functions as default values... but I'm a bit
|
||||
# lazy :p
|
||||
if part.has_key("Content-Disposition"):
|
||||
resp["Content-Disposition"] = \
|
||||
re.sub("\s", "", part["Content-Disposition"])
|
||||
else:
|
||||
resp["Content-Disposition"] = \
|
||||
"attachment; filename=%s" % request.GET["fname"]
|
||||
resp["Content-Length"] = len(payload)
|
||||
return resp
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def move(request):
|
||||
@@ -204,7 +216,10 @@ def mark(request, name):
|
||||
def empty(request, name):
|
||||
if name == parameters.get_user(request.user, "TRASH_FOLDER"):
|
||||
get_imapconnector(request).empty(name)
|
||||
return ajax_simple_response(dict(status="ok"))
|
||||
content = "<div class='alert alert-info'>%s</div>" % _("Empty mailbox")
|
||||
return ajax_simple_response(dict(
|
||||
status="ok", listing=content
|
||||
))
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
@@ -239,7 +254,7 @@ def newfolder(request, tplname="webmail/folder2.html"):
|
||||
"withunseen" : False,
|
||||
"selectonly" : True}
|
||||
|
||||
ctx["folders"] = mbc.getfolders(request.user)
|
||||
ctx["folders"] = mbc.getmboxes(request.user)
|
||||
if request.method == "POST":
|
||||
form = FolderForm(request.POST)
|
||||
if form.is_valid():
|
||||
@@ -267,7 +282,7 @@ def editfolder(request, tplname="webmail/folder2.html"):
|
||||
"action_classes" : "submit",
|
||||
"withunseen" : False,
|
||||
"selectonly" : True}
|
||||
ctx["folders"] = mbc.getfolders(request.user)
|
||||
ctx["folders"] = mbc.getmboxes(request.user)
|
||||
if request.method == "POST":
|
||||
form = FolderForm(request.POST)
|
||||
if form.is_valid():
|
||||
@@ -388,7 +403,7 @@ def render_mboxes_list(request, imapc):
|
||||
curmbox = request.session.get("mbox", "INBOX")
|
||||
return _render_to_string(request, "webmail/folders.html", {
|
||||
"selected" : curmbox,
|
||||
"folders" : imapc.getfolders(request.user),
|
||||
"folders" : imapc.getmboxes(request.user),
|
||||
"withunseen" : True
|
||||
})
|
||||
|
||||
@@ -544,9 +559,22 @@ def viewmail(request):
|
||||
@needs_mailbox()
|
||||
def submailboxes(request):
|
||||
topmailbox = request.GET.get('topmailbox', '')
|
||||
mboxes = get_imapconnector(request).getfolders(request.user, topmailbox)
|
||||
mboxes = get_imapconnector(request).getmboxes(request.user, topmailbox)
|
||||
return ajax_simple_response(dict(status="ok", mboxes=mboxes))
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def check_unseen_messages(request):
|
||||
mboxes = request.GET.get("mboxes", None)
|
||||
if not mboxes:
|
||||
raise WebmailError(_("Invalid request"))
|
||||
mboxes = mboxes.split(",")
|
||||
counters = dict()
|
||||
imapc = get_imapconnector(request)
|
||||
for mb in mboxes:
|
||||
counters[mb] = imapc.unseen_messages(mb)
|
||||
return ajax_simple_response(dict(status="ok", counters=counters))
|
||||
|
||||
@login_required
|
||||
@needs_mailbox()
|
||||
def newindex(request):
|
||||
@@ -587,10 +615,10 @@ def newindex(request):
|
||||
if not request.is_ajax():
|
||||
request.session["lastaction"] = None
|
||||
imapc = get_imapconnector(request)
|
||||
response["mboxes"] = render_mboxes_list(request, imapc)
|
||||
imapc.getquota(curmbox)
|
||||
response["refreshrate"] = \
|
||||
int(parameters.get_user(request.user, "REFRESH_INTERVAL")) * 1000
|
||||
response["mboxes"] = render_mboxes_list(request, imapc)
|
||||
int(parameters.get_user(request.user, "REFRESH_INTERVAL"))
|
||||
response["quota"] = ImapListing.computequota(imapc)
|
||||
if response["quota"] < 50:
|
||||
response["quotalevel"] = "success"
|
||||
|
||||
@@ -155,7 +155,7 @@ class EmailListing(object):
|
||||
def render(self, request, pageid=1, **kwargs):
|
||||
page = self.paginator.getpage(pageid)
|
||||
if not page:
|
||||
listing = _("This folder contains no message")
|
||||
listing = "<div class='alert alert-info'>%s</div>" % _("Empty mailbox")
|
||||
else:
|
||||
listing = self.fetch(request, page.id_start, page.id_stop)
|
||||
return dict(listing=listing, navbar=self.render_navbar(page, self.baseurl))
|
||||
|
||||
+4
-2
@@ -120,8 +120,10 @@ class Table(object):
|
||||
trcpt = 0
|
||||
for row in rows:
|
||||
nrow = {"id" : row[self.idkey], "cols" : [], "trcpt" : trcpt}
|
||||
if row.has_key("options"):
|
||||
nrow["options"] = row["options"]
|
||||
for name in ["style", "options"]:
|
||||
if row.has_key(name):
|
||||
nrow[name] = row[name]
|
||||
|
||||
for c in self.columns:
|
||||
newcol = {"name" : c.name}
|
||||
for key in ["width", "align", "cssclass"]:
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Simple poller plugin.
|
||||
*
|
||||
* Sends a request at a given frequency.
|
||||
*/
|
||||
var Poller = function(url, options) {
|
||||
this.initialize(url, options);
|
||||
};
|
||||
|
||||
Poller.prototype = {
|
||||
constructor: Poller,
|
||||
|
||||
defaults: {
|
||||
interval: 2000,
|
||||
success_cb: null,
|
||||
error_cb: null
|
||||
},
|
||||
|
||||
initialize: function(url, options) {
|
||||
this.url = url;
|
||||
this.options = $.extend({}, this.defaults, options);
|
||||
this.running_request = false;
|
||||
this.reset();
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
setTimeout($.proxy(this.send_request, this), this.options.interval);
|
||||
},
|
||||
|
||||
send_request: function() {
|
||||
var poller = this;
|
||||
var args = "";
|
||||
|
||||
if (this.options.args != undefined) {
|
||||
args = "?";
|
||||
if (typeof this.options.args === "function") {
|
||||
args += this.options.args();
|
||||
} else {
|
||||
args += this.options.args;
|
||||
}
|
||||
}
|
||||
this.running_request = true;
|
||||
$.ajax({
|
||||
url: this.url + args,
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
poller.running_request = false;
|
||||
if (data.status == "ok") {
|
||||
if (poller.options.success_cb) {
|
||||
poller.options.success_cb(data);
|
||||
}
|
||||
poller.reset();
|
||||
} else {
|
||||
$("body").notify("error", data.respmsg);
|
||||
if (poller.options.error_cb) {
|
||||
poller.options.error_cb(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -16,6 +16,7 @@
|
||||
<script src="{{ MEDIA_URL }}js/common.js" type="text/javascript"></script>
|
||||
<script src="{{ MEDIA_URL }}js/confirmation.js" type="text/javascript"></script>
|
||||
<script src="{{ MEDIA_URL }}js/notify.js" type="text/javascript"></script>
|
||||
<script src="{{ MEDIA_URL }}js/poller.js" type="text/javascript"></script>
|
||||
{% url django.views.i18n.javascript_catalog as catalog_url %}
|
||||
<script type="text/javascript" src="{{ catalog_url }}"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
Reference in New Issue
Block a user