def leave_project(self, proj_id): """ remove membership by marking the membership object as inactive """ if not self._can_leave(proj_id): return False if self._is_only_admin(proj_id): proj = self.portal.projects[proj_id] proj_title = unicode(proj.Title(), 'utf-8') # accessor always will return ascii only_admin_msg = _(u'psm_leave_project_admin', u'You are the only administrator of "${proj_title}". You can\'t leave this ${project_noun} without appointing another.', mapping={u'proj_title':proj_title, u'project_noun':self.project_noun}) self.addPortalStatusMessage(only_admin_msg) return False if self._apply_transition_to(proj_id, 'deactivate'): mship = self._membership_for_proj(proj_id) notify(LeftProjectEvent(mship)) return True else: msg = _(u'psm_cannot_leave_project', u'You cannot leave this ${project_noun}.', mapping={u'project_noun': self.project_noun}) self.addPortalStatusMessage(msg) return False
def handle_login(self): if self.login_pending_member(): return id_ = self.request.get('__ac_name') if self.loggedin: self.addPortalStatusMessage(_(u'psm_signin_welcome', u'Welcome! You have signed in.')) self.update_credentials(id_) self.membertool.setLoginTimes() # member area only created if it doesn't yet exist; # createMemberArea method will trigger # notifyMemberAreaCreated skin script, which will trigger # opencore.siteui.member.initializeMemberArea # @@ move out of skins!!! self.membertool.createMemberArea() member = self.loggedinmember try: if member.getLast_login_time() == member.getLogin_time(): # first login notify(FirstLoginEvent(member, self.request)) except AttributeError: # we're not a remember-based user pass destination = self.destination() return self.redirect(destination) self.addPortalStatusMessage(_(u'psm_check_username_password', u'Please check your username and password. If you still have trouble, you can <a href="forgot">retrieve your sign in information</a>.'))
def handle_request(self): userid_or_email = self.request.get("__ac_name") if not userid_or_email: self.addPortalStatusMessage(_(u'psm_enter_username_email', u"Please enter your username or email address.")) return False site_url = getToolByName(self.context, 'portal_url')() brain = self.brain_for_userid_or_email(userid_or_email) if brain is not None: userid = brain.getId if self.is_pending(getUserName=userid): self.redirect('%s/resend-confirmation?member=%s' % (site_url, userid)) return False reset_url = site_url + self.reset_url(userid) if email_confirmation(): self._mailPassword(brain.getEmail, reset_url) self.addPortalStatusMessage(_(u'psm_forgot_login', u'Your username is ${user_id}. If you would like to reset your password, please check your email account for further instructions.', mapping={u'user_id': userid})) else: self.redirect(reset_url) return True # else the user isn't found self.addPortalStatusMessage(u"We can't find your account.") return False
def versions(self): versions = self.request.form.get('version_id') req_error = None if not versions or not isinstance(versions, list) or len(versions) < 2: req_error = _(u'version_error_choose_two', u'Please choose the two versions you would like to compare.') elif len(versions) > 2: req_error = _(u'version_error_too_many', u'Please choose only two versions to compare.') if not req_error: versions.sort() old_version_id, new_version_id = self.sort_versions(*versions) try: old_version = self.get_version(old_version_id) new_version = self.get_version(new_version_id) except ArchivistRetrieveError: req_error = _(u'version_invalid_id', u'Please choose a valid version.') if req_error: # redirect to input page on error self.addPortalStatusMessage(req_error) raise Redirect('%s/history' % self.context.absolute_url()) return dict(old=(old_version_id, old_version), new=(new_version_id, new_version))
def request_membership(self, targets=None, fields=None): """ Delegates to the team object and handles destination. """ from opencore import recaptcha if not recaptcha.confirm(self.request['REMOTE_ADDR'], self.request.form.get("recaptcha_challenge_field"), self.request.form.get("recaptcha_response_field")): self.addPortalStatusMessage("Invalid CAPTCHA, please try again.") self.redirect(self.context.absolute_url()) return if self.login_pending_member(): return joined = False ac_name = self.request.form.get("__ac_name") id_ = self.request.form.get("id") if self.loggedin: # PAS will kick in, request will be "logged in" if form's login snippet is filled out correctly # so the user might be really logged in, or might have provided valid credentials w/request from opencore.interfaces.pending_requests import IRequestMembership req_msg = self.request.form.get("request-message") joined = IRequestMembership(self.team).join(req_msg) self._login() # conditionally set cookie if valid credentials were provided elif id_: # trying to create a member # create member mem = self._create() if isinstance(mem, dict): # failure, so return the errors to be rendered return mem from opencore.interfaces.pending_requests import IPendingRequests req_bucket = getMultiAdapter((mem, self.portal.projects), IPendingRequests) req_msg = self.request.form.get("request-message") req_bucket.addRequest(self.context.getId(), request_message=req_msg) self.template = None # don't render the form before the redirect self.redirect(self.context.absolute_url()) return elif ac_name: # trying to login, but failed self.addPortalStatusMessage(_(u'psm_check_username_password', u'Please check your username and password. If you still have trouble, you can <a href="forgot">retrieve your sign in information</a>.')) return else: self.add_status_message(u"You must login or create an account") return if not joined: psmid = 'already_proj_member' self.add_status_message(_(u'team_already_proj_member', u'You are already a pending or active member of ${project_title}.', mapping={'project_title':self.context.Title().decode('utf-8')})) self.template = None # don't render the form before the redirect self.redirect(self.context.absolute_url()) return self.add_status_message(_(u'team_proj_join_request_sent', u'Your request to join "${project_title}" has been sent to the ${project_noun} administrator(s).', mapping={u'project_title':self.context.Title().decode('utf-8'), u'project_noun': self.project_noun})) self.template = None # don't render the form before the redirect self.redirect(self.context.absolute_url())
def version_title(self, version_id, rollback=False): if version_id == 0: return _(u'version_initial_title', u"Initial Version") elif version_id == self.current_id() and not rollback: return _(u'version_current_title', u"Current Version") else: return _(u'version_requested_title', "Version ${version_num}", mapping={'version_num': version_id + 1})
def handle_request(self, target=None, fields=None): # Get the tool used to normalize strings putils = getToolByName(self.context, 'plone_utils') result = self.validate_form(creation=True) if not result: return title, workflow, archive, mailto, managers = result private_archives = "private_list" in self.request.form sync_project_membership = "sync_project_membership" in self.request.form # Try to create a mailing list using the mailto address to see if it's going to be valid lists_folder = self.context try: lists_folder.invokeFactory(OpenMailingList.portal_type, mailto) except BadRequest: self.errors['mailto'] = _(u'list_create_duplicate_error', u'The requested list prefix is already taken.') self.add_status_message(_(u'psm_correct_errors_below', u'Please correct the errors indicated below.')) return list = lists_folder._getOb(mailto) list.managers = tuple(managers) self._assign_local_roles_to_managers(list) list.setDescription(unicode(self.request.form.get('description',''), 'utf-8')) old_workflow_type = list.list_type new_workflow_type = workflow_to_mlist_type(workflow) notify(ListTypeChanged(list, old_workflow_type.list_marker, new_workflow_type.list_marker)) list.archived = archive list.private_archives = private_archives if sync_project_membership: alsoProvides(list, ISyncWithProjectMembership) self.template = None #subscribe user to list sub_list = IWriteMembershipList(list) current_user = unicode(self.loggedinmember.getId()) sub_list.subscribe(current_user) s_message_mapping = {'title': title} s_message = _(u'list_created', u'"${title}" has been created.', mapping=s_message_mapping) self.add_status_message(s_message) list.reindexObject() self.redirect(list.absolute_url())
def validate(self): view = self.__parent__ request = self.request if not request.form.has_key('termsofuse'): return {"termsofuse": _(u"You must agree to the terms of use")} if not request.form['termsofuse']: return {"termsofuse": _(u"You must agree to the terms of use")} return {}
def change_role(self, mem_ids, action): assert action in ("promote", "demote") changes = [] team = self.team if action == "demote": wrong_role = "ProjectMember" new_role = MEMBER_ROLES else: wrong_role = "ProjectAdmin" new_role = ADMIN_ROLES for mem_id in mem_ids: if team.getHighestTeamRoleForMember(mem_id) == wrong_role: continue team.setTeamRolesForMember(mem_id, new_role) changes.append(mem_id) self.team.reindexTeamSpaceSecurity() if len(changes) == 0: if action == "demote": msg = u"Select one or more project admins to demote to members" else: msg = u"Select one or more project members to promote to admins" self.add_status_message(msg) if self.request.form.get("mode", None) == "async": return {} return self.redirect('%s/manage-team' % self.context.absolute_url()) for mem_id in changes: if action == "demote": transient_msg = 'You are no longer an admin of' else: transient_msg = 'You are now an admin of' self._add_transient_msg_for(mem_id, transient_msg) if action == "demote": status_msg = _(u'demote_to_member', mapping={'name': mem_id}) else: status_msg = _(u'promote_to_admin', mapping={'name': mem_id}) self.add_status_message(status_msg) if self.request.form.get("mode", None) == "async": if action == "demote": return {"role": "member"} else: return {"role": "administrator"} return self.redirect('%s/manage-team' % self.context.absolute_url())
def confirmDeleteList(self): form = self.request.form psm = _(u'psm_mailing_list_deletion_cancelled', u"Mailing list deletion cancelled.") list_id = form.get('list_id', '') confirm = form.get('%s_confirm_delete' % list_id, 'false') if confirm == 'true': self.context.manage_delObjects(ids=[list_id]) psm = _(u'psm_mailing_list_deleted', u"Mailing list deleted.") psm = translate(psm) url = "%s?portal_status_message=%s" % (self.context.absolute_url(), psm) self.request.RESPONSE.redirect(url)
def handle_post(self): member = self._pending_member() if not member: return email = '' if self.request.form.get('resend_email'): email = member.getEmail() if self.request.form.get('new_email'): email = self.request.form.get('email', '') msg = member.validate_email(email) if msg: email = '' else: member.setEmail(email) mem_name = member.getFullname() mem_name = mem_name or member.getId() if email: self._send_mail_to_pending_user(mem_name, email, self._confirmation_url(member)) mfrom = self.portal.getProperty('email_from_address') msg = _(u'psm_new_activation', mapping={u'email':email, u'mfrom':mfrom}) self.addPortalStatusMessage(msg)
def deny_handler(self, targets, fields=None): assert len(targets) == 1 proj_id = targets[0] if not self._apply_transition_to(proj_id, 'reject_by_owner'): return {} id_ = self.viewed_member_info['id'] # there must be a better way to get the last wf transition which was an invite... right? wftool = self.get_tool("portal_workflow") team = self.get_tool("portal_teams").getTeamById(proj_id) mship = team.getMembershipByMemberId(id_) wf_id = wftool.getChainFor(mship)[0] wf_history = mship.workflow_history.get(wf_id) spurned_admin = [i for i in wf_history if i['review_state'] == 'pending'][-1]['actor'] transient_msgs = ITransientMessage(self.portal) member_url = u'%s/%s' % (getToolByName(self.context, 'portal_url')(), member_path(id_)) project_url = self.project_url(proj_id) msg = _(u'tmsg_decline_invite', u'<a href="${member_url}">${id}</a> has declined your invitation to join <a href="${project_url}">${proj_id}</a>', mapping={u'id':id_, u'project_url':project_url, u'proj_id':proj_id, u'member_url':member_url}) transient_msgs.store(spurned_admin, "membership", msg) elt_id = '%s_invitation' % proj_id return ajax_update(elt_id, self.nupdates())
def _mailPassword(self, email, reset_url): if not self.membertool.checkPermission('Mail forgotten password', self): raise Unauthorized, "Mailing forgotten passwords has been disabled." try: pwt = self.get_tool("portal_password_reset") mail_text = _(u'email_forgot_password', u'You requested a password reset for your ${portal_title} account. If you did not request this information, please ignore this message.\n\nTo change your password, please visit the following URL: ${url}', mapping={u'url':reset_url}) sender = EmailSender(self, secureSend=True) sender.sendEmail(mto=email, msg=mail_text, subject=_(u'email_forgot_password_subject', u'${portal_title} - Password reset', mapping={u'portal_title':self.portal_title()})) except SMTPRecipientsRefused: # Don't disclose email address on failure # XXX is this needed? raise SMTPRecipientsRefused('Recipient address rejected by server')
def store(self, mem_id, category, msg): """ This takes an OBJECT and stores it on a per-user basis. If we receive a string, we're going to see if it's a message id and attempt to translate it. If not, WE WILL LEAVE IT ALONE!!! """ cat = self._category_annot(mem_id, category) try: new_id = cat.maxKey() + 1 except ValueError: new_id = 0 # Try to turn a str into a Message. if isinstance(msg, basestring) and not isinstance(msg, Message): # The reason for that comparison is: if it's already a # Message, then -- very confusingly -- Message is a # subclass of basestring ... but calling _() on a Message # returns an instance that compares equal to the original # but translates differently! No kidding. WTF. msg = _(msg) if isinstance(msg, Message): msg = translate(msg, context=self.site_root) if isinstance(msg, Message) or isinstance(msg, basestring): cleaner = Cleaner() msg = cleaner.clean_html(msg) # clean_html wraps plain text messages in a paragraph tag. If # that has happened, we'll remove it to restore the original message. if msg.startswith('<p>'): msg = msg[3:-4] cat[new_id] = msg
def menudata(self): site_url = getToolByName(self.context, 'portal_url')() menudata = ( {'content': _(u'Sign in'), 'href': '%s/login' % site_url, }, {'content': _(u'Create account'), 'href': '%s/join' % site_url, }, ) return menudata
def _clean_html(self, html): """ delegate cleaning of html to lxml .. sort of """ ## FIXME: we should have some way of notifying the user about tags that were removed config = getUtility(IProvideSiteConfig) whitelist = config.get('embed_whitelist', default='').split(',') whitelist = [ x.strip() for x in whitelist if x.strip() ] cleaner = MonkeyCleaner(host_whitelist=whitelist, safe_attrs_only=False) # stolen from lxml.html.clean if isinstance(html, basestring): return_string = True doc = fromstring(html) else: return_string = False doc = copy.deepcopy(html) # see #2793 cleaner(doc) for el, url in cleaner.tags_removed: self.add_status_message(_("psm_wiki_content_stripped", u"Your markup '${stripped_tag}' was removed " "because ${unallowed_host} is not an allowed host " "for embedded content on this site. ", mapping={u'stripped_tag':el.tag.upper(), u'unallowed_host':url})) if return_string: return tounicode(doc) else: return doc
def save_wiki_history(self): self.status.progress_descr = _(u'Saving wiki history') project = self.context tempdir = tempfile.mkdtemp() tmpdbdir = os.path.join(tempdir, "tmpdbs") repodir = os.path.join(tempdir, "bzr_repos") checkoutdir = os.path.join(tempdir, "bzr_checkouts") try: converter = bzrbackend.WikiConverter( project, tmpdbdir, repodir, checkoutdir) clonedir, filename_map = converter.convert() root_len = len(os.path.abspath(clonedir)) for root, dirs, files in os.walk(clonedir): relative_root = os.path.abspath(root)[root_len:].strip(os.sep) archive_root = os.path.join( self.context_dirname, 'wiki_history', relative_root) for f in files: fullpath = os.path.join(root, f) archive_name = os.path.join(archive_root, f) self.zipfile.write(fullpath, archive_name) self.zipfile.writestr(os.path.join(self.context_dirname, "wiki_history_filenames.json"), json.dumps(filename_map)) finally: shutil.rmtree(tempdir)
def save_files(self): self.status.progress_descr = _(u'Saving images and file attachments') for afile in self.catalog(portal_type=("FileAttachment", "Image"), path=self.path): obj = afile.getObject() # Files should be saved in a directory named for # the page they're attached to, so links work. relative_path = afile.getPath()[len(self.path):].lstrip('/') out_path = '%s/pages/%s' % (self.context_dirname, relative_path) if isinstance(obj.data, basestring): self.zipfile.writestr(out_path, str(obj)) continue # For large files, to avoid loading it all into memory, # we iterate over the data chain and write directly to disk, # then zip it afterward. data = obj.data temp = tempfile.NamedTemporaryFile(delete=True) try: while data is not None: temp.write(data) data = data.next self.zipfile.write(temp.name, out_path) finally: temp.close()
def queue(self, queue): if self.running or self.queued: return queue.put(self.name) self.state = self.QUEUED self.updatetime = self.starttime = datetime.datetime.utcnow() self.progress_descr = _(u'') self.filename = None
def check_portrait(self, member, portrait): try: member.setPortrait(portrait) except (ValueError, IOError): # must have tried to upload an unsupported filetype self.addPortalStatusMessage(_(u'psm_choose_image', u'Unsupported image format. Please use JPEG, GIF, BMP, or non-interlaced PNG format.')) transaction.abort() return False return True
def listen_featurelet_installed(proj, event): """need to create a default discussion mailing list and subscribe all project members to the list""" proj_id = proj.getId() proj_title = proj.Title().decode('utf-8') ml_id = '%s-discussion' % proj_id address = '%s%s' % (ml_id, getSuffix()) # need to verify that a mailing list with this name isn't already created portal = getToolByName(proj, 'portal_url').getPortalObject() ll = getUtility(IListLookup, context=portal) if ll.getListForAddress(address) is not None: # XXX we'll just silently fail for now, not sure what else we can do # psm maybe? return # XXX we need a request utility request = proj.REQUEST # invokeFactory depends on the title being set in the request ml_title = u'%s discussion' % (proj_title) request.set('title', ml_title) lists_folder = proj.lists.aq_inner lists_folder.invokeFactory(OpenMailingList.portal_type, ml_id) ml = lists_folder[ml_id] ml.mailto = ml_id ms_tool = getToolByName(proj, 'portal_membership') cur_mem_id = unicode(ms_tool.getAuthenticatedMember().getId()) ml.managers = (cur_mem_id,) ml.setDescription(translate(_(u'discussion_list_desc', u'Discussion list for this ${project_noun}, consisting of all ${project_noun} members.', mapping={'project_noun':project_noun()}), context=request)) # http://www.openplans.org/projects/opencore/lists/opencore-dev/archive/2009/04/1239543233615/forum_view from zope.interface import alsoProvides alsoProvides(ml, ISyncWithProjectMembership) notify(ObjectCreatedEvent(ml)) memlist = IWriteMembershipList(ml) cat = getToolByName(portal, 'portal_catalog') teams = getToolByName(portal, 'portal_teams') try: team = teams[proj_id] except KeyError: # if the team doesn't exist # then nobody is on the project yet # so we only need to subscribe the current user memlist.subscribe(cur_mem_id) return active_states = teams.getDefaultActiveStates() team_path = '/'.join(team.getPhysicalPath()) mships = cat(portal_type='OpenMembership', review_state=active_states, path=team_path) for mship in mships: memlist.subscribe(mship.getId)
def finish(self, path): # XXX fire an event? if self.failed: # Ok, so i guess it is a lame sort of state machine. return self.path = path self.filename = os.path.basename(path) self.state = self.DONE self.progress_descr = _(u'Export finished') self.updatetime = datetime.datetime.utcnow()
def __call__(self): """ if already member of project, redirect appropriately """ # if already a part of the team, redirect to project home page if self.member_info.get('id') in self.team.getActiveMemberIds(): self.add_status_message(_(u'team_already_project_member', u'You are already a member of this ${project_noun}.', mapping={u'project_noun': self.project_noun})) self.redirect('%s?came_from=%s' % (self.context.absolute_url(), self.request.getURL())) return super(RequestMembershipView, self).__call__()
def send_mail(self, status): site = getSite() try: project = site.restrictedTraverse('projects/%s' % status.name) except: log_exception("Couldn't access project %s -- not sending email" % status.name) return try: username = status.user() except: log_exception("Couldn't parse cookie %s to extract username -- not sending email" % status.cookie) return mto = [username] if status.succeeded: msg_subs = {'project': project.Title(), 'url': '%s/export/%s' % ( status.context_url, status.filename), 'portal_title': site.Title() } msg = _(u'Your export of project "${project}" has finished.\n\nYou can download it by clicking this link: ${url}\n\nCheers,\nThe ${portal_title} Team') subject = _(u'Project export complete') elif status.failed: msg_subs = {'project': project.Title(), 'portal_title': site.Title() } msg = _(u'Your export of project "${project}" has failed because of an internal error. We apologize for the inconvenience.\n\nThe site administration has been notified of the problem and we will let you know as soon as the error has been fixed.\nThe ${portal_title} Team') subject = _(u'Project export failed, sorry!') mto.append(site.getProperty('email_from_address')) else: return IEmailSender(site).sendMail( mto=mto, msg=msg, subject=subject, **msg_subs)
def confirm(self, member): """Move member into the confirmed workflow state""" member = IHandleMemberWorkflow(member) if not member.is_unconfirmed(): self.addPortalStatusMessage(_(u'psm_not_pending_account', u'You have tried to activate an account that is not pending confirmation. Please sign in normally.')) return False member.confirm() return True
def change_password(self, target=None, fields=None): """allows members to change their password""" passwd_curr = self.request.form.get('passwd_curr') password = self.request.form.get('password') password2 = self.request.form.get('password2') self.request.form['confirm_password'] = password2 member = self.viewedmember() mem_id = self.viewed_member_info['id'] if not member.verifyCredentials({'login': mem_id, 'password': passwd_curr}): self.addPortalStatusMessage(_(u'psm_check_old_password', u'Please check the old password you entered.')) return if self.validate_password_form(password, password2, member): member._setPassword(password) self.addPortalStatusMessage(_(u'psm_password_changed', u'Your password has been changed.'))
def logout(self, redirect=None): logout = self.cookie_logout self.invalidate_session() self.add_status_message(_(u'psm_signed_out', u"You have signed out.")) if redirect is None: redirect = self.login_url self.redirect("%s" %redirect)
def send(self, target=None, fields=None): sender = IEmailSender(self.portal) mto = self.viewed_member_info.get('email') mfrom = self.loggedinmember.getId() msg = self.request.form['message'] subject = self.request.form['subject'] sender.sendMail(mto, msg=msg, subject=subject, mfrom=mfrom) self.addPortalStatusMessage(_(u'psm_message_sent_to_user', u'Message sent.')) ret_url = self.request.form.get('ret_url') or self.context.absolute_url() return self.redirect(ret_url)
def save_list_archives(self): self.status.progress_descr = _(u'Saving mailing lists') try: listfol = self.context['lists'] except KeyError: logger.error("No lists subfolder on %s" % self.context.getId()) return real_site = getSite() for mlistid, mlist in listfol.objectItems(): # XXX filter more? logger.info("exporting %s" % mlistid) setSite(mlist) # Needed to get the export adapter. mlistid = badchars.sub('_', mlistid) # Cargo-culted from listen/browser/import_export.py em = getAdapter(mlist, IMailingListMessageExport, name='mbox') # Nooo don't do it all in memory, we have seen some huge archives. #file_data = em.export_messages() or '' #self.zipfile.writestr('%s/lists/%s.mbox' % (self.context_dirname, mlistid), # file_data) tmpfd, tmpname = em.export_messages_to_tempfile() self.zipfile.write(tmpname, '%s/lists/%s/archive.mbox' % (self.context_dirname, mlistid)) os.unlink(tmpname) del(tmpfd) # Now the list subscribers. logger.info("exporting subscribers.csv for %s" % mlistid) es = getAdapter(mlist, IMailingListSubscriberExport, name='csv') file_data = es.export_subscribers( include_allowed_senders=True) or '' csv_path = '%s/lists/%s/subscribers.csv' % (self.context_dirname, mlistid) self.zipfile.writestr(csv_path, file_data) # Now the metadata and preferences. logger.info("exporting settings.ini for %s" % mlistid) list_info = { 'id': mlist.getId(), 'type': mlist_type_to_workflow(mlist), 'mailto': mlist.mailto, 'managers': mlist.managers, 'archive_setting': mlist_archive_policy(mlist), 'title': mlist.Title(), 'description': mlist.Description(), 'creation_date': mlist.CreationDate(), 'creator': mlist.Creator(), 'sync_with_project': ISyncWithProjectMembership.providedBy(mlist), 'context': self.context.getId(), 'private_archives': mlist.private_archives, } conf_path = '%s/lists/%s/settings.ini' % (self.context_dirname, mlistid) self.zipfile.writestr(conf_path, mlist_conf(list_info)) setSite(real_site) logger.info("finished %s" % mlistid)
def menudata(self): mem_data = self.member_info site_url = getToolByName(self.context, 'portal_url')() menudata = ( {'content': _(u'Sign out'), 'href': '%s/logout' % site_url, }, ) return menudata