Bivio::Biz::Model::RealmMail
# Copyright (c) 2006-2013 bivio Software, Inc. All Rights Reserved. # $Id$ package Bivio::Biz::Model::RealmMail; use strict; use Bivio::Base 'Model.RealmBase'; my($_D) = b_use('Bivio.Die'); my($_E) = b_use('Type.Email'); my($_F) = b_use('Biz.File'); my($_FP) = b_use('Type.FilePath'); my($_HANDLERS) = b_use('Biz.Registrar')->new; my($_I) = b_use('Mail.Incoming'); my($_LM) = b_use('Biz.ListModel'); my($_MAIL_READ) = ${b_use('Auth.PermissionSet')->from_array(['MAIL_READ'])}; my($_MAX_EMAIL) = b_use('Type.Email')->get_width; my($_MAX_NAME) = b_use('Type.DisplayName')->get_width; my($_MFN) = b_use('Type.MailFileName'); my($_MI) = b_use('Type.MessageId'); my($_MS) = b_use('Type.MailSubject'); my($_MV) = b_use('Type.MailVisibility'); my($_RF) = b_use('Model.RealmFile'); my($_RI) = b_use('Agent.RequestId'); sub access_is_public_only { my($proto, $req) = @_; return $req->get('auth_realm')->does_user_have_permissions($_MAIL_READ, $req) ? 0 : 1; } sub assert_mail_visibility { my($proto, $req) = @_; $proto->throw_die('FORBIDDEN', 'Always is private') if $_MV->row_tag_get($req)->eq_always_is_private && $proto->access_is_public_only($req); return; } sub assert_original_visibility { my($proto, $req) = @_; $proto->throw_die('FORBIDDEN', 'Not authorized to view original') unless _original_visibility($req); return; } sub audit_threads { my($self) = @_; $self->new_other('RealmMailList') ->do_iterate( sub { my($it) = @_; my($rm) = $it->get_model('RealmMail'); $rm->update( _thread_values( $rm, $_I->new( $_RF->get_content($it, 'RealmMail.'), ), $rm->get_shallow_copy, ), ); return 1; }, { order_by => [qw(RealmFile.modified_date_time asc)], }, ); return; } sub can_view_original { my(undef, $req) = @_; return _original_visibility($req); } sub create_from_rfc822 { my($self, $rfc822) = @_; my($die); my($res) = $_D->catch( sub {_create($self, _create_file($self, $rfc822))}, \$die, ); return $res unless $die; $_F->write( $_FP->join( $self->simple_package_name, $_RI->current($self->req) . '.eml', ), $rfc822, ); $die->throw; # DOES NOT RETURN } #TODO: this needs a better name that describes the function. Probably is just "delete", # just like when you delete a file, it has to audit properly. sub delete_message { my($self) = @_; my($this_id) = $self->get('realm_file_id'); my($parent_id) = $self->get('thread_parent_id'); if ($parent_id) { $self->new_other('RealmMail')->do_iterate(sub { my($it) = @_; $it->update({ thread_root_id => $self->get('thread_root_id'), thread_parent_id => $parent_id, }); return 1; }, { 'thread_parent_id' => $this_id, }); } else { my($new_root_id); $_LM->new_anonymous({ primary_key => [[qw(RealmMail.realm_file_id RealmFile.realm_file_id)]], order_by => [{ name => 'RealmFile.modified_date_time', sort_order => 1, }], other => [['RealmMail.thread_parent_id', [$this_id]]], })->do_iterate(sub { my($it) = @_; my($new_parent_id); if ($new_root_id) { $new_parent_id = $new_root_id; } else { $new_root_id = $it->get('RealmMail.realm_file_id'); } $it->get_model('RealmMail')->update({ thread_root_id => $new_root_id, thread_parent_id => $new_parent_id, }); return 1; }); #TODO: Delete the CRMThread if there are no mail messages my($crmt) = $self->new_other('CRMThread'); $crmt->unauth_load({ thread_root_id => $self->get('thread_root_id'), }); if (defined($new_root_id)) { $crmt->update({ thread_root_id => $new_root_id, }) if $crmt->is_loaded; $self->new_other('RealmMail')->do_iterate( sub { my($it) = @_; $it->update({ thread_root_id => $new_root_id, thread_parent_id => $it->get('thread_parent_id'), }); return 1; }, { 'thread_root_id' => $this_id, }); } else { $crmt->delete if $crmt->is_loaded; } } $self->delete; $self->new_other('RealmMailBounce')->delete_all({ realm_file_id => $self->get('realm_file_id'), }); $self->new_other('RealmFile')->delete({ realm_file_id => $self->get('realm_file_id'), override_is_read_only => 1, override_versioning => 1, }); return; } sub get_mail_part_list { my(undef, $delegator, $prefix) = shift->delegated_args(@_); return $delegator->new_other('MailPartList')->load_all({ parent_id => $delegator->get(($prefix || '') . 'realm_file_id'), }); } sub get_rfc822 { return $_RF->get_content(shift(@_)); } sub internal_initialize { my($self) = @_; return $self->merge_initialize_info($self->SUPER::internal_initialize, { version => 1, table_name => 'realm_mail_t', as_string_fields => [qw(message_id)], columns => { realm_file_id => ['RealmFile.realm_file_id', 'PRIMARY_KEY'], # Index on realm_id, message_id. Does it need to be unique? message_id => ['MessageId', 'NOT_NULL'], thread_root_id => ['RealmFile.realm_file_id', 'NOT_NULL'], thread_parent_id => ['RealmFile.realm_file_id', 'NONE'], from_email => ['Email', 'NOT_NULL'], from_display_name => ['DisplayName', 'NONE'], subject => ['MailSubject', 'NOT_NULL'], subject_lc => ['MailSubject', 'NOT_NULL'], }, other => [ [qw(realm_file_id RealmFile.realm_file_id)], [qw(realm_id RealmOwner.realm_id)], ], auth_id => 'realm_id', }); } sub register { shift; $_HANDLERS->push_object(@_); return; } sub to_subject_lc { my($proto, $subject) = @_; return lc($_MS->clean_and_trim($subject, 1)); } sub update { my($self, $values) = @_; if (defined($values->{subject})) { $values->{subject} = $_MS->clean_and_trim($values->{subject}); $values->{subject_lc} = $self->to_subject_lc($values->{subject}); } $self->die( $values, ': must set both thread_parent_id and thread_root_id or neither', ) if exists($values->{thread_parent_id}) xor exists($values->{thread_root_id}); return shift->SUPER::update(@_); } sub _call_handlers { $_HANDLERS->call_fifo(shift, [@_]); return; } sub _create { my($self, $in, $file) = @_; my($email, $name) = $in->get_from; $name ||= $_E->get_local_part($email); $self->create( _thread_values($self, $in, { map(($_ => $file->get($_)), qw(realm_id realm_file_id)), message_id => $_MI->from_literal_or_die( $_MI->clean_and_trim($in->get_message_id)), from_email => substr(lc($email), 0, $_MAX_EMAIL), from_display_name => substr($name || '', 0, $_MAX_NAME), subject => $_MS->clean_and_trim($in->get_subject), subject_lc => lc($_MS->clean_and_trim($in->get_subject, 1)), }, $in), ); _call_handlers(handle_mail_post_create => $self, $in, $file); return $in; } sub _create_file { my($self, $rfc822) = @_; _call_handlers(handle_mail_pre_create_file => $self, $rfc822); my($in) = $_I->new($rfc822); my($date) = $in->get_date_time; my($rf) = $self->new_other('RealmFile'); return ( $in, $rf->create_with_content({ override_is_read_only => 1, path => $_MFN->to_unique_absolute( $date, $_MV->row_tag_get($self->req)->eq_always_is_public, ), user_id => _user_id($self, $in), modified_date_time => $date, }, $rfc822), ); } sub _original_visibility { my($req) = @_; return $req->unsafe_get('auth_user_id') && $req->can_user_execute_task( 'GROUP_USER_LIST', $req->get('auth_id'), ); } sub _thread_values { my($self, $in, $values) = @_; my($l) = $self->new_other('RealmMailReferenceList') ->load_first_from_incoming($in); if ($l && ! $_MS->subject_lc_matches( $values->{subject_lc}, $l->get('RealmMail.subject_lc'))) { $l = undef; } $values->{thread_parent_id} = $l ? $l->get('RealmMail.realm_file_id') : undef; $values->{thread_root_id} = $l ? $l->get('RealmMail.thread_root_id') : $values->{realm_file_id}; return $values; } sub _user_id { my($self, $in) = @_; my($f) = $in->get_from; my($e); if ($e = $self->ureq('Model.Email')) { return $e->get('realm_id') if $e->field_equals(email => $f); } my($user_id) = $in->get_from_user_id($self->req); return $user_id ? $user_id : $self->req('auth_user_id') || $self->new_other('RealmUser') ->get_any_online_admin->get('realm_id'); } 1;