Changeset 171


Ignore:
Timestamp:
Jan 29, 2007 11:38:31 PM (6 years ago)
Author:
dkg
Message:

JPDD: created initial broadcast e-mail infrastructure.

Location:
trunk/jpdd
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/jpdd/class.dkg.site.php

    r169 r171  
    135135 
    136136    // we're relying on a functioning mail() function. 
    137     function mail($type, $to,$subj,$body, $hdrs = array()) { 
     137    // returning true means it actually was queued for delivery. 
     138    // returning false means it was never queued. 
     139    function mail($type, $to,$subj,$body, $hdrs = array(), $broadcast_id = NULL) { 
    138140      if (!is_array($hdrs)) 
    139141        $hdrs = array($hdrs); 
     
    142144 
    143145      // log it: 
    144       $this->executeSQL('INSERT INTO mail_log (mail_from, mail_to, subject, body, extra_headers, message_type, actually_sent) VALUES ('.$this->escStr($this->_site_email_from).','. 
     146      $this->executeSQL('INSERT INTO mail_log (mail_from, mail_to, subject, body, extra_headers, message_type, actually_sent, broadcast_id) VALUES ('.$this->escStr($this->_site_email_from).','. 
    145147                        $this->escStr($to).','. 
    146148                        $this->escStr($subj).','. 
     
    148150                        $this->escStr($hdrs).','. 
    149151                        $this->escStr($type).','. 
    150                         ($this->_actually_send_email ? 'true' : 'false'). 
     152                        ($this->_actually_send_email ? 'true' : 'false').','. 
     153                        $this->intOrDefault($broadcast_id). 
    151154                        ')', false); 
    152155      if ($this->_actually_send_email) 
    153         mail($to, $subj, $body, $hdrs); 
     156        return mail($to, $subj, $body, $hdrs); 
     157      else 
     158        return false; // we didn't actually send the mail. 
    154159    } 
    155160 
     
    185190 
    186191    function getAllowedActions() { 
    187       return array('edit', 'login', 'logout', 'resetpass', 'newacct'); 
     192      return array('edit', 'login', 'logout', 'resetpass', 'newacct', 'broadcast'); 
    188193    } 
    189194     
     
    963968          } 
    964969        } 
     970      } elseif ($this->_action == 'broadcast') { 
     971        if ($this->_authenticated_user->HasAllOfThesePrivileges('Send Broadcast')) { 
     972          if ($_SERVER['REQUEST_METHOD'] == 'POST') { 
     973            if ($_POST['submit'] == 'Send') { 
     974              return $this->sendBroadcastEmails(); 
     975            } else { 
     976              return $this->getBroadcastEmailForm(); 
     977            } 
     978          } else { 
     979            return $this->getBroadcastEmailForm(); 
     980          } 
     981        } else 
     982          return $this->permissionDenied('Broadcast not allowed.'); 
    965983      } else { 
    966984        return $this->getMainContentExtended(); 
    967985      } 
     986    } 
     987 
     988    function getBroadcastSelectors() { 
     989      // override to return an array that maps internal keys to 
     990      // visible labels and SQL statements.  For example: 
     991      return array('all' => array('description' => 'Every account with a confirmed e-mail', 
     992                                  'sql' => 'SELECT * FROM person WHERE pass IS NOT NULL')); 
     993    } 
     994 
     995    function getAllowedTemplates() { 
     996      // override to return an array that maps selectors to 
     997      // array('description' = "human readable", 'function' => actual 
     998      // php function) 
     999      return array('[[NAME]]' => array('description' => 'The person\'s name', 
     1000                                       'function' => create_function('$x', 'return $x->getTitle();'))); 
     1001    } 
     1002     
     1003    function getAllowedTemplateLegend() { 
     1004      $temps = $this->getAllowedTemplates(); 
     1005      return "<div class=\"template-legend\">Allowed personalization terms:<dl>\n". 
     1006        join('', array_map(create_function('$x,$y', 'return "<dt>".$x."</dt>\n<dd>".$y["description"]."</dd>\n";'), 
     1007                           array_keys($temps), $temps))."</dl>\n</div>\n"; 
     1008    } 
     1009     
     1010    function getBroadcastSelectorInput() { 
     1011      $selectors = $this->getBroadcastSelectors(); 
     1012      return '<select name="selector"> 
     1013'.join("\n", array_map(create_function('$v, $l', 'global $dkg_site; return "<option value=\"".$v."\">".$l["description"]." (".$dkg_site->getValueFromSQL("SELECT COUNT(*) AS count FROM (".$l["sql"].") AS selector", "count").")</option>";'), array_keys($selectors), $selectors)).' 
     1014</select> 
     1015'; 
     1016    } 
     1017     
     1018    // $txt can be either a string or an array of strings.  the return 
     1019    // value will match. 
     1020    function applyBroadcastTemplate($txt, $user) { 
     1021      $temps = $this->getAllowedTemplates(); 
     1022      $vals = array(); 
     1023      reset($temps); 
     1024      while(list($temp,$x) = each($temps)) { 
     1025        $vals[] = $x['function']($user); 
     1026      } 
     1027      return str_ireplace(array_keys($temps), $vals, $txt); 
     1028    } 
     1029 
     1030    function sendBroadcastEmails() { 
     1031      if ($_SERVER['REQUEST_METHOD'] != 'POST') { 
     1032        $this->addWarning('You cannot send broadcasts with a GET request.'); 
     1033        return ''; 
     1034      } 
     1035 
     1036      $subj = $_POST['subject']; 
     1037      $content = $_POST['content']; 
     1038       
     1039      $selector_name = $_POST['selector']; 
     1040      $selectors = $this->getBroadcastSelectors(); 
     1041      if (!array_key_exists($selector_name, $selectors)) { 
     1042        $this->addWarning('You chose an unavailable selector!'); 
     1043        return ''; 
     1044      } 
     1045      $selector = $selectors[$selector_name]; 
     1046 
     1047      // store this broadcast template in the database: 
     1048      $bid = $this->insertBroadcast($subj, $content, $selector_name); 
     1049 
     1050      // we're gonna grab some peops: 
     1051      $map = $this->prepClass('person'); 
     1052      $cname = $map['classname']; 
     1053      $objs = $this->getSeriesFromSQL($selector['sql'], $cname); 
     1054      $attempted = 0; 
     1055      $succeeded = 0; 
     1056      reset($objs); 
     1057      while(list(,$obj) = each($objs)) { 
     1058        $to = $obj->_email; 
     1059        $subj = trim(str_replace("\n", '', $this->applyBroadcastTemplate($subj, $obj))); 
     1060        $body = trim(wordwrap($this->applyBroadcastTemplate($content, $obj))); 
     1061        $attempted++; 
     1062        if ($this->mail('broadcast', $to,$subj,$body, array(), $bid)) 
     1063          $succeeded++; 
     1064      } 
     1065      return $succeeded.' out of '.$attempted.' mails sent.'; 
     1066    } 
     1067 
     1068    function getBroadcastEmailForm() { 
     1069      $subj = ''; 
     1070      $content = ''; 
     1071      $preview = ''; 
     1072      $ret = ''; 
     1073 
     1074      if ($_SERVER['REQUEST_METHOD'] == 'POST') { 
     1075        if ($this->_actually_send_email) 
     1076          $this->addWarning('Sending e-mail is actually enabled.  Please be careful with this!'); 
     1077        else  
     1078          $ret .= '<div>Sending e-mail is currently disabled.  Messages will be logged, but not actually sent.</div>'; 
     1079        $subj = $_POST['subject']; 
     1080        $content = $_POST['content']; 
     1081         
     1082        $selector = $_POST['selector']; 
     1083        $selectors = $this->getBroadcastSelectors(); 
     1084        if (!array_key_exists($selector, $selectors)) { 
     1085          $this->addWarning('You chose an unavailable selector!'); 
     1086          return ''; 
     1087        } 
     1088        $selector = $selectors[$selector]; 
     1089         
     1090        $preview = '<fieldset><legend>Preview Example:</legend>'; 
     1091         
     1092        // we're gonna grab a person: 
     1093        $map = $this->prepClass('person'); 
     1094        $cname = $map['classname']; 
     1095        $data = $this->getSingletonFromSQL($selector['sql'].' LIMIT 1', false); 
     1096        if (is_null($data)) { 
     1097          $preview .= '<span class="error">There are no selectors which match <q>'.$selector['description'].'</q></span>'; 
     1098        } else { 
     1099          $obj = new $cname(array('data' => $data)); 
     1100          $preview .= '<pre>To: '.$obj->_email.' 
     1101Subject: '.htmlentities(trim(str_replace("\n", '', $this->applyBroadcastTemplate($subj, $obj)))).' 
     1102 
     1103'.htmlentities(trim(wordwrap($this->applyBroadcastTemplate($content, $obj)))).'</pre>'; 
     1104        } 
     1105        $preview .= '</fieldset>'; 
     1106      } 
     1107 
     1108      return $ret.'<form name="broadcast" action="'.$this->Path('broadcast').'" method="post"> 
     1109'.$this->getFormTokenHiddenInput().' 
     1110<fieldset><legend>Send to:</legend> 
     1111'.$this->getBroadcastSelectorInput().' 
     1112</fieldset> 
     1113'.$preview.' 
     1114<fieldset><legend>Message Content:</legend> 
     1115'.$this->getAllowedTemplateLegend().' 
     1116<label>Subject:<br /> 
     1117<input name="subject" type="text" size="60" value="'.htmlentities($subj).'" /><br /> 
     1118<label>Body:<br /> 
     1119<textarea name="content" rows="20" cols="72">'.htmlentities($content). 
     1120        '</textarea> 
     1121</label><br/> 
     1122<input type="submit" class="submit" name="submit" value="Preview"/> 
     1123'.($_SERVER['REQUEST_METHOD'] == 'POST' ? '<input class="submit" type="submit" name="submit" value="Send"/>' : '').' 
     1124</fieldset> 
     1125</form>'; 
    9681126    } 
    9691127 
     
    10331191        } 
    10341192 
     1193    function insertBroadcast($subj, $body, $selector) { 
     1194       $this->executeSQL('INSERT INTO broadcast (subj, body, selector, sender_id) VALUES ('.$this->escStr($subj).', '. 
     1195                         $this->escStr($body).', '. 
     1196                         $this->escStr($selector).', '. 
     1197                         (int)$this->_authenticated_user->getID().')'); 
     1198       // return the last id from the log: 
     1199       return (int)$this->getValueFromSQL('SELECT currval(\'broadcast_id_seq\') AS broadcast_id', 'broadcast_id'); 
     1200    } 
     1201 
    10351202    function log($data) { 
    10361203       $this->executeSQL('INSERT INTO log (data, backtrace, servervars) VALUES ('.$this->escStr($data).', '. 
  • trunk/jpdd/class.jpdd.php

    r168 r171  
    4545    } 
    4646 
     47 
     48    function getBroadcastSelectors() { 
     49      // override to return an array that maps internal keys to 
     50      // visible labels and SQL statements.  For example: 
     51      return array_merge(array('dkg' => array('description' => 'People named Gillmor', 
     52                                              'sql' => 'SELECT * FROM person WHERE last_name = '.$this->escStr('Gillmor'))), 
     53                         parent::getBroadcastSelectors() 
     54                         ); 
     55    } 
     56 
    4757    function getNavLink($title,$targ) { 
    4858      return '<a '.(($this->_action == $targ || (in_array($this->_action, array('view', 'edit')) && $this->_type == $targ)) ? 'class="current" ' : '').'href='.$this->Path($targ).'>'.$title.'</a>'; 
     
    6373        if ($this->_authenticated_user->hasAllOfThesePrivileges('Update Workshops', 'Edit People', 'Edit Categories')) 
    6474          $links['Review Signups'] = 'overview'; 
     75        if ($this->_authenticated_user->hasAllOfThesePrivileges('Send Broadcast')) 
     76          $links['E-mail Alert'] = 'broadcast'; 
    6577      } else { 
    6678        if ($this->_action != 'newacct') 
  • trunk/jpdd/config.inc.php

    r169 r171  
    3030  // if this is set to false, no e-mail will actually go out, though 
    3131  // it will be written to the log.  Defaults to true. 
    32   'actually_send_email' => false, 
     32  'actually_send_email' => true, 
    3333); 
  • trunk/jpdd/sql/basics.sql

    r144 r171  
    7272-- 
    7373 
    74 SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('privilege', 'id'), 7, true); 
     74SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('privilege', 'id'), 8, true); 
    7575 
    7676 
     
    87876       List People     This allows users to see a list of all people signed up for the conference 
    88887       Edit Categories This allows users to modify the available categories 
     898       Send Broadcast  This allows users to broadcast templated messages to groups of signed-up users 
    8990\. 
    9091 
  • trunk/jpdd/sql/db.sql

    r169 r171  
    8181 
    8282ALTER TABLE public.audience_by_organization OWNER TO dkg; 
     83 
     84-- 
     85-- Name: broadcast; Type: TABLE; Schema: public; Owner: dkg; Tablespace:  
     86-- 
     87 
     88CREATE TABLE broadcast ( 
     89    id serial NOT NULL, 
     90    subj character varying NOT NULL, 
     91    body text NOT NULL, 
     92    selector character varying NOT NULL, 
     93    sender_id integer NOT NULL 
     94); 
     95 
     96 
     97ALTER TABLE public.broadcast OWNER TO dkg; 
    8398 
    8499-- 
     
    123138    whattime timestamp with time zone DEFAULT now() NOT NULL, 
    124139    actually_sent boolean DEFAULT true NOT NULL, 
    125     message_type character varying DEFAULT 'newacct'::character varying NOT NULL 
     140    message_type character varying DEFAULT 'newacct'::character varying NOT NULL, 
     141    broadcast_id integer 
    126142); 
    127143 
     
    272288 
    273289-- 
     290-- Name: broadcast_pkey; Type: CONSTRAINT; Schema: public; Owner: dkg; Tablespace:  
     291-- 
     292 
     293ALTER TABLE ONLY broadcast 
     294    ADD CONSTRAINT broadcast_pkey PRIMARY KEY (id); 
     295 
     296 
     297-- 
    274298-- Name: category_pkey; Type: CONSTRAINT; Schema: public; Owner: dkg; Tablespace:  
    275299-- 
     
    453477ALTER TABLE ONLY attendance 
    454478    ADD CONSTRAINT attendance_workshop_id_fkey FOREIGN KEY (workshop_id) REFERENCES workshop(id); 
     479 
     480 
     481-- 
     482-- Name: broadcast_sender_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: dkg 
     483-- 
     484 
     485ALTER TABLE ONLY broadcast 
     486    ADD CONSTRAINT broadcast_sender_id_fkey FOREIGN KEY (sender_id) REFERENCES person(id); 
     487 
     488 
     489-- 
     490-- Name: mail_log_broadcast_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: dkg 
     491-- 
     492 
     493ALTER TABLE ONLY mail_log 
     494    ADD CONSTRAINT mail_log_broadcast_id_fkey FOREIGN KEY (broadcast_id) REFERENCES broadcast(id); 
    455495 
    456496 
     
    574614 
    575615-- 
     616-- Name: broadcast; Type: ACL; Schema: public; Owner: dkg 
     617-- 
     618 
     619REVOKE ALL ON TABLE broadcast FROM PUBLIC; 
     620REVOKE ALL ON TABLE broadcast FROM dkg; 
     621GRANT ALL ON TABLE broadcast TO dkg; 
     622GRANT INSERT ON TABLE broadcast TO "www-data"; 
     623 
     624 
     625-- 
     626-- Name: broadcast_id_seq; Type: ACL; Schema: public; Owner: dkg 
     627-- 
     628 
     629REVOKE ALL ON TABLE broadcast_id_seq FROM PUBLIC; 
     630REVOKE ALL ON TABLE broadcast_id_seq FROM dkg; 
     631GRANT ALL ON TABLE broadcast_id_seq TO dkg; 
     632GRANT SELECT,UPDATE ON TABLE broadcast_id_seq TO "www-data"; 
     633 
     634 
     635-- 
    576636-- Name: category; Type: ACL; Schema: public; Owner: dkg 
    577637-- 
  • trunk/jpdd/web/styles.css

    r157 r171  
    102102} 
    103103 
     104 
     105.template-legend {  
     106  background: silver; 
     107  color: black; 
     108  padding: 1em; 
     109  margin: 0.5em; 
     110} 
     111.template-legend dl dt {  
     112 font-weight: bold; 
     113} 
     114.template-legend dl dd {  
     115 font-style: italic; 
     116} 
     117 
    104118@media print {  
    105119 
Note: See TracChangeset for help on using the changeset viewer.