Merge pull request #4981 from totten/master-cbf2
[civicrm-core.git] / CRM / Contact / Form / Task / SMSCommon.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2014
32 * $Id$
33 *
34 */
35
36 /**
37 * This class provides the common functionality for sending sms to
38 * one or a group of contact ids.
39 */
40 class CRM_Contact_Form_Task_SMSCommon {
41 const RECIEVED_SMS_ACTIVITY_SUBJECT = "SMS Received";
42
43 public $_contactDetails = array();
44
45 public $_allContactDetails = array();
46
47 public $_toContactPhone = array();
48
49
50 /**
51 * @param CRM_Core_Form $form
52 */
53 public static function preProcessProvider(&$form) {
54 $form->_single = FALSE;
55 $className = CRM_Utils_System::getClassName($form);
56
57 if (property_exists($form, '_context') &&
58 $form->_context != 'search' &&
59 $className == 'CRM_Contact_Form_Task_SMS'
60 ) {
61 $form->_single = TRUE;
62 }
63
64 $providersCount = CRM_SMS_BAO_Provider::activeProviderCount();
65
66 if (!$providersCount) {
67 CRM_Core_Error::statusBounce(ts('There are no SMS providers configured, or no SMS providers are set active'));
68 }
69
70 if ($className == 'CRM_Activity_Form_Task_SMS') {
71 $activityCheck = 0;
72 foreach ($form->_activityHolderIds as $value) {
73 if (CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $value, 'subject', 'id') != self::RECIEVED_SMS_ACTIVITY_SUBJECT) {
74 $activityCheck++;
75 }
76 }
77 if ($activityCheck == count($form->_activityHolderIds)) {
78 CRM_Core_Error::statusBounce(ts("The Reply SMS Could only be sent for activities with '%1' subject.",
79 array(1 => self::RECIEVED_SMS_ACTIVITY_SUBJECT)
80 ));
81 }
82 }
83 }
84
85 /**
86 * Build the form object
87 *
88 *
89 * @param CRM_Core_Form $form
90 *
91 * @return void
92 */
93 public static function buildQuickForm(&$form) {
94
95 $toArray = array();
96
97 $providers = CRM_SMS_BAO_Provider::getProviders(NULL, NULL, TRUE, 'is_default desc');
98
99 $providerSelect = array();
100 foreach ($providers as $provider) {
101 $providerSelect[$provider['id']] = $provider['title'];
102 }
103 $suppressedSms = 0;
104 //here we are getting logged in user id as array but we need target contact id. CRM-5988
105 $cid = $form->get('cid');
106
107 if ($cid) {
108 $form->_contactIds = array($cid);
109 }
110
111 $to = $form->add('text', 'to', ts('To'), array('class' => 'huge'), TRUE);
112 $form->add('text', 'activity_subject', ts('Name The SMS'), array('class' => 'huge'), TRUE);
113
114 $toSetDefault = TRUE;
115 if (property_exists($form, '_context') && $form->_context == 'standalone') {
116 $toSetDefault = FALSE;
117 }
118
119 // when form is submitted recompute contactIds
120 $allToSMS = array();
121 if ($to->getValue()) {
122 $allToPhone = explode(',', $to->getValue());
123
124 $form->_contactIds = array();
125 foreach ($allToPhone as $value) {
126 list($contactId, $phone) = explode('::', $value);
127 if ($contactId) {
128 $form->_contactIds[] = $contactId;
129 $form->_toContactPhone[] = $phone;
130 }
131 }
132 $toSetDefault = TRUE;
133 }
134
135 //get the group of contacts as per selected by user in case of Find Activities
136 if (!empty($form->_activityHolderIds)) {
137 $extendTargetContacts = 0;
138 $invalidActivity = 0;
139 $validActivities = 0;
140 foreach ($form->_activityHolderIds as $key => $id) {
141 //valid activity check
142 if (CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $id, 'subject', 'id') != self::RECIEVED_SMS_ACTIVITY_SUBJECT) {
143 $invalidActivity++;
144 continue;
145 }
146
147 $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name');
148 $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
149 //target contacts limit check
150 $ids = array_keys(CRM_Activity_BAO_ActivityContact::getNames($id, $targetID));
151
152 if (count($ids) > 1) {
153 $extendTargetContacts++;
154 continue;
155 }
156 $validActivities++;
157 $form->_contactIds = empty($form->_contactIds) ? $ids : array_unique(array_merge($form->_contactIds, $ids));
158 }
159
160 if (!$validActivities) {
161 $errorMess = "";
162 if ($extendTargetContacts) {
163 $errorMess = ts('One selected activity consists of more than one target contact.', array(
164 'count' => $extendTargetContacts,
165 'plural' => '%count selected activities consist of more than one target contact.',
166 ));
167 }
168 if ($invalidActivity) {
169 $errorMess = ($errorMess ? ' ' : '');
170 $errorMess .= ts('The selected activity is invalid.', array(
171 'count' => $invalidActivity,
172 'plural' => '%count selected activities are invalid.',
173 ));
174 }
175 CRM_Core_Error::statusBounce(ts("%1: SMS Reply will not be sent.", array(1 => $errorMess)));
176 }
177 }
178
179 if (is_array($form->_contactIds) && !empty($form->_contactIds) && $toSetDefault) {
180 $returnProperties = array(
181 'sort_name' => 1,
182 'phone' => 1,
183 'do_not_sms' => 1,
184 'is_deceased' => 1,
185 'display_name' => 1,
186 );
187
188 list($form->_contactDetails) = CRM_Utils_Token::getTokenDetails($form->_contactIds,
189 $returnProperties,
190 FALSE,
191 FALSE
192 );
193
194 // make a copy of all contact details
195 $form->_allContactDetails = $form->_contactDetails;
196
197 foreach ($form->_contactIds as $key => $contactId) {
198 $value = $form->_contactDetails[$contactId];
199
200 //to check if the phone type is "Mobile"
201 $phoneTypes = CRM_Core_OptionGroup::values('phone_type', TRUE, FALSE, FALSE, NULL, 'name');
202
203 if (CRM_Utils_System::getClassName($form) == 'CRM_Activity_Form_Task_SMS') {
204 //to check for "if the contact id belongs to a specified activity type"
205 $actDetails = CRM_Activity_BAO_Activity::getContactActivity($contactId);
206 if (self::RECIEVED_SMS_ACTIVITY_SUBJECT !=
207 CRM_Utils_Array::retrieveValueRecursive($actDetails, 'subject')
208 ) {
209 $suppressedSms++;
210 unset($form->_contactDetails[$contactId]);
211 continue;
212 }
213 }
214
215 if ((isset($value['phone_type_id']) && $value['phone_type_id'] != CRM_Utils_Array::value('Mobile', $phoneTypes)) || $value['do_not_sms'] || empty($value['phone']) || !empty($value['is_deceased'])) {
216
217 //if phone is not primary check if non-primary phone is "Mobile"
218 if (!empty($value['phone'])
219 && $value['phone_type_id'] != CRM_Utils_Array::value('Mobile', $phoneTypes) && empty($value['is_deceased'])
220 ) {
221 $filter = array('do_not_sms' => 0);
222 $contactPhones = CRM_Core_BAO_Phone::allPhones($contactId, FALSE, 'Mobile', $filter);
223 if (count($contactPhones) > 0) {
224 $mobilePhone = CRM_Utils_Array::retrieveValueRecursive($contactPhones, 'phone');
225 $form->_contactDetails[$contactId]['phone_id'] = CRM_Utils_Array::retrieveValueRecursive($contactPhones, 'id');
226 $form->_contactDetails[$contactId]['phone'] = $mobilePhone;
227 $form->_contactDetails[$contactId]['phone_type_id'] = CRM_Utils_Array::value('Mobile', $phoneTypes);
228 }
229 else {
230 $suppressedSms++;
231 unset($form->_contactDetails[$contactId]);
232 continue;
233 }
234 }
235 else {
236 $suppressedSms++;
237 unset($form->_contactDetails[$contactId]);
238 continue;
239 }
240 }
241
242 if (isset($mobilePhone)) {
243 $phone = $mobilePhone;
244 }
245 elseif (empty($form->_toContactPhone)) {
246 $phone = $value['phone'];
247 }
248 else {
249 $phone = CRM_Utils_Array::value($key, $form->_toContactPhone);
250 }
251
252 if ($phone) {
253 $toArray[] = array(
254 'text' => '"' . $value['sort_name'] . '" (' . $phone . ')',
255 'id' => "$contactId::{$phone}",
256 );
257 }
258 }
259
260 if (empty($toArray)) {
261 CRM_Core_Error::statusBounce(ts('Selected contact(s) do not have a valid Phone, or communication preferences specify DO NOT SMS, or they are deceased'));
262 }
263 }
264
265 //activity related variables
266 if (isset($invalidActivity)) {
267 $form->assign('invalidActivity', $invalidActivity);
268 }
269 if (isset($extendTargetContacts)) {
270 $form->assign('extendTargetContacts', $extendTargetContacts);
271 }
272
273 $form->assign('toContact', json_encode($toArray));
274 $form->assign('suppressedSms', $suppressedSms);
275 $form->assign('totalSelectedContacts', count($form->_contactIds));
276
277 $form->add('select', 'sms_provider_id', ts('From'), $providerSelect, TRUE);
278
279 CRM_Mailing_BAO_Mailing::commonCompose($form);
280
281 if ($form->_single) {
282 // also fix the user context stack
283 if ($form->_context) {
284 $url = CRM_Utils_System::url('civicrm/dashboard', 'reset=1');
285 }
286 else {
287 $url = CRM_Utils_System::url('civicrm/contact/view',
288 "&show=1&action=browse&cid={$form->_contactIds[0]}&selectedChild=activity"
289 );
290 }
291
292 $session = CRM_Core_Session::singleton();
293 $session->replaceUserContext($url);
294 $form->addDefaultButtons(ts('Send SMS'), 'upload', 'cancel');
295 }
296 else {
297 $form->addDefaultButtons(ts('Send SMS'), 'upload');
298 }
299
300 $form->addFormRule(array('CRM_Contact_Form_Task_SMSCommon', 'formRule'), $form);
301 }
302
303 /**
304 * Form rule
305 *
306 * @param array $fields
307 * The input form values.
308 * @param array $dontCare
309 * @param array $self
310 * Additional values form 'this'.
311 *
312 * @return bool|array
313 * true if no errors, else array of errors
314 */
315 public static function formRule($fields, $dontCare, $self) {
316 $errors = array();
317
318 $template = CRM_Core_Smarty::singleton();
319
320 if (empty($fields['sms_text_message'])) {
321 $errors['sms_text_message'] = ts('Please provide Text message.');
322 }
323 else {
324 if (!empty($fields['sms_text_message'])) {
325 $messageCheck = CRM_Utils_Array::value('sms_text_message', $fields);
326 $messageCheck = str_replace("\r\n", "\n", $messageCheck);
327 if ($messageCheck && (strlen($messageCheck) > CRM_SMS_Provider::MAX_SMS_CHAR)) {
328 $errors['sms_text_message'] = ts("You can configure the SMS message body up to %1 characters", array(1 => CRM_SMS_Provider::MAX_SMS_CHAR));
329 }
330 }
331 }
332
333 //Added for CRM-1393
334 if (!empty($fields['saveTemplate']) && empty($fields['saveTemplateName'])) {
335 $errors['saveTemplateName'] = ts("Enter name to save message template");
336 }
337
338 return empty($errors) ? TRUE : $errors;
339 }
340
341 /**
342 * Process the form after the input has been submitted and validated
343 *
344 *
345 * @param CRM_Core_Form $form
346 *
347 * @return void
348 */
349 public static function postProcess(&$form) {
350
351 // check and ensure that
352 $thisValues = $form->controller->exportValues($form->getName());
353
354 $fromSmsProviderId = $thisValues['sms_provider_id'];
355
356 // process message template
357 if (!empty($thisValues['saveTemplate']) || !empty($thisValues['updateTemplate'])) {
358 $messageTemplate = array(
359 'msg_text' => $thisValues['sms_text_message'],
360 'is_active' => TRUE,
361 );
362
363 if (!empty($thisValues['saveTemplate'])) {
364 $messageTemplate['msg_title'] = $thisValues['saveTemplateName'];
365 CRM_Core_BAO_MessageTemplate::add($messageTemplate);
366 }
367
368 if (!empty($thisValues['template']) && !empty($thisValues['updateTemplate'])) {
369 $messageTemplate['id'] = $thisValues['template'];
370 unset($messageTemplate['msg_title']);
371 CRM_Core_BAO_MessageTemplate::add($messageTemplate);
372 }
373 }
374
375 // format contact details array to handle multiple sms from same contact
376 $formattedContactDetails = array();
377 $tempPhones = array();
378
379 foreach ($form->_contactIds as $key => $contactId) {
380 $phone = $form->_toContactPhone[$key];
381
382 if ($phone) {
383 $phoneKey = "{$contactId}::{$phone}";
384 if (!in_array($phoneKey, $tempPhones)) {
385 $tempPhones[] = $phoneKey;
386 if (!empty($form->_contactDetails[$contactId])) {
387 $formattedContactDetails[] = $form->_contactDetails[$contactId];
388 }
389 }
390 }
391 }
392
393 // $smsParams carries all the arguments provided on form (or via hooks), to the provider->send() method
394 // this gives flexibity to the users / implementors to add their own args via hooks specific to their sms providers
395 $smsParams = $thisValues;
396 unset($smsParams['sms_text_message']);
397 $smsParams['provider_id'] = $fromSmsProviderId;
398 $contactIds = array_keys($form->_contactDetails);
399 $allContactIds = array_keys($form->_allContactDetails);
400
401 list($sent, $activityId, $countSuccess) = CRM_Activity_BAO_Activity::sendSMS($formattedContactDetails,
402 $thisValues,
403 $smsParams,
404 $contactIds
405 );
406
407 if ($countSuccess > 0) {
408 CRM_Core_Session::setStatus(ts('One message was sent successfully.', array(
409 'plural' => '%count messages were sent successfully.',
410 'count' => $countSuccess,
411 )), ts('Message Sent', array('plural' => 'Messages Sent', 'count' => $countSuccess)), 'success');
412 }
413
414 if (is_array($sent)) {
415 // At least one PEAR_Error object was generated.
416 // Display the error messages to the user.
417 $status = '<ul>';
418 foreach ($sent as $errMsg) {
419 $status .= '<li>' . $errMsg . '</li>';
420 }
421 $status .= '</ul>';
422 CRM_Core_Session::setStatus($status, ts('One Message Not Sent', array(
423 'count' => count($sent),
424 'plural' => '%count Messages Not Sent',
425 )), 'info');
426 }
427 else {
428 //Display the name and number of contacts for those sms is not sent.
429 $smsNotSent = array_diff_assoc($allContactIds, $contactIds);
430
431 if (!empty($smsNotSent)) {
432 $not_sent = array();
433 foreach ($smsNotSent as $index => $contactId) {
434 $displayName = $form->_allContactDetails[$contactId]['display_name'];
435 $phone = $form->_allContactDetails[$contactId]['phone'];
436 $contactViewUrl = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid=$contactId");
437 $not_sent[] = "<a href='$contactViewUrl' title='$phone'>$displayName</a>";
438 }
439 $status = '(' . ts('because no phone number on file or communication preferences specify DO NOT SMS or Contact is deceased');
440 if (CRM_Utils_System::getClassName($form) == 'CRM_Activity_Form_Task_SMS') {
441 $status .= ' ' . ts("or the contact is not part of the activity '%1'", array(1 => self::RECIEVED_SMS_ACTIVITY_SUBJECT));
442 }
443 $status .= ')<ul><li>' . implode('</li><li>', $not_sent) . '</li></ul>';
444 CRM_Core_Session::setStatus($status, ts('One Message Not Sent', array(
445 'count' => count($smsNotSent),
446 'plural' => '%count Messages Not Sent',
447 )), 'info');
448 }
449 }
450 }
451
452 }