1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35:
36:
37: Yii::import('application.models.X2Model');
38:
39: 40: 41: 42:
43: class Actions extends X2Model {
44:
45: 46: 47: 48:
49: const ACTION_INDEX_PAGE_SIZE = 20;
50:
51: const COLORS_DROPDOWN_ID = 123;
52:
53: 54: 55: 56:
57: const ASSOCIATION_TYPE_MULTI = '__multiple__';
58:
59: public $skipActionTimers = false;
60:
61: public $supportsWorkflow = false;
62:
63: 64: 65: 66:
67: public static $emailTypes = array(
68: 'email', 'emailFrom','emailOpened','email_invoice', 'email_quote');
69:
70: public $verifyCode;
71: public $actionDescriptionTemp = '';
72:
73: private $metaDataTemp = array (
74: 'eventSubtype' => null,
75: 'eventStatus' => null,
76:
77: );
78:
79: private static $_priorityLabels;
80:
81:
82: private static $withActionText = true;
83:
84: 85: 86: 87: 88:
89: public static function associateAction (X2Model $model, array $attributes) {
90: $now = time ();
91: $action = new Actions;
92: $action->setAttributes (array_merge (array (
93: 'assignedTo' => $model->assignedTo,
94: 'visibility' => '1',
95: 'associationType' => X2Model::getAssociationType (get_class ($model)),
96: 'associationId' => $model->id,
97: 'associationName' => $model->name,
98: 'createDate' => $now,
99: 'lastUpdated' => $now,
100: 'completeDate' => $now,
101: 'complete' => 'Yes',
102: 'updatedBy' => 'admin',
103: ), $attributes), false);
104: return $action->save();
105: }
106:
107: 108: 109: 110:
111: public static function getFormTypes () {
112: return array_merge (
113: array ('Actions'),
114: array ('CalendarEventFormModel'),
115: array_map (function ($type) {
116: return ucfirst ($type).'FormModel';
117: }, array (
118: 'action',
119: 'time',
120: 'event',
121: 'products',
122: 'call',
123: 'note',
124: )));
125: }
126:
127: 128: 129: 130:
131: public static function model($className = __CLASS__){
132: return parent::model($className);
133: }
134:
135: 136: 137:
138: public function tableName(){
139: return 'x2_actions';
140: }
141:
142: public function behaviors(){
143: return array(
144: 'X2LinkableBehavior' => array(
145: 'class' => 'X2LinkableBehavior',
146: 'module' => 'actions'
147: ),
148: 'X2TimestampBehavior' => array('class' => 'X2TimestampBehavior'),
149: 'X2FlowTriggerBehavior' => array('class' => 'X2FlowTriggerBehavior'),
150: 'TagBehavior' => array('class' => 'TagBehavior'),
151: 'ERememberFiltersBehavior' => array(
152: 'class' => 'application.components.ERememberFiltersBehavior',
153: 'defaults' => array(),
154: 'defaultStickOnClear' => false
155: ),
156: 'permissions' => array('class' => Yii::app()->params->modelPermissions),
157:
158: );
159: }
160:
161: 162: 163:
164: public function rules(){
165: return array_merge (
166: $this->getBehaviorRules (),
167: array(
168: array('allDay', 'boolean'),
169: array('associationId,associationType','requiredAssoc'),
170: array('createDate, completeDate, lastUpdated', 'numerical', 'integerOnly' => true),
171: array(
172: 'id,assignedTo,actionDescription,visibility,associationId,associationType,'.
173: 'associationName,dueDate,priority,type,createDate,complete,reminder,'.
174: 'completedBy,completeDate,lastUpdated,updatedBy,color,subject', 'safe'),
175: array(
176: 'verifyCode', 'captcha', 'allowEmpty' => !CCaptcha::checkRequirements(),
177: 'on' => 'guestCreate'),
178: array ('notificationUsers', 'validateNotificationUsers'),
179: )
180: );
181: }
182:
183: public function validateNotificationUsers ($attribute) {
184: $value = $this->$attribute;
185: return in_array ($value, array ('me', 'assigned', 'both'));
186: }
187:
188: 189: 190:
191: public function relations(){
192: return array_merge(parent::relations(), array(
193: 'workflow' => array(self::BELONGS_TO, 'Workflow', 'workflowId'),
194: 'workflowStage' => array(self::BELONGS_TO, 'WorkflowStage', 'stageNumber'),
195: 'actionMetaData' => array(self::HAS_ONE, 'ActionMetaData', 'actionId'),
196: 'actionText' => array(self::HAS_ONE, 'ActionText', 'actionId'),
197:
198: ));
199: }
200:
201:
202: public function setX2Fields(&$data, $filter = false, $bypassPermissions=false) {
203: if (isset ($data['lineitem'])) {
204: $this->setActionLineItems ($data['lineitem']);
205: }
206: return parent::setX2Fields ($data, $filter, $bypassPermissions);
207: }
208:
209: 210: 211: 212: 213: 214: 215:
216: public function multiAssociateWith (X2Model $model) {
217: if ($this->associationType !== self::ASSOCIATION_TYPE_MULTI) {
218: throw new CException (
219: 'Attempting to multi-associate action with single association type');
220: }
221: $joinModel = ActionToRecord::model ()->findByAttributes (array (
222: 'actionId' => $this->id,
223: 'recordId' => $model->id,
224: 'recordType' => get_class ($model),
225: ));
226: if ($joinModel) return -1;
227: $joinModel = new ActionToRecord;
228: $joinModel->setAttributes (array (
229: 'actionId' => $this->id,
230: 'recordId' => $model->id,
231: 'recordType' => get_class ($model),
232: ), false);
233: return $joinModel->save ();
234: }
235:
236: 237: 238: 239:
240: public function getMultiassociations ($modelClass = null) {
241: $multiAssociations = array();
242: $attributes = array ('actionId' => $this->id);
243: if (!is_null($modelClass))
244: $attributes['recordType'] = $modelClass;
245: $joinModels = ActionToRecord::model ()->findAllByAttributes ($attributes);
246: foreach ($joinModels as $model) {
247: $modelRecord = X2Model::model($model->recordType)->findByPk ($model->recordId);
248: $multiAssociations[$model->recordType][] = $modelRecord;
249: }
250: return $multiAssociations;
251: }
252:
253: 254: 255:
256: public function convertToMultiassociation() {
257: $success = true;
258: if ($this->associationId) {
259: $joinModel = ActionToRecord::model ()->findByAttributes (array (
260: 'actionId' => $this->id,
261: 'recordId' => $this->associationId,
262: 'recordType' => $this->associationType,
263: ));
264: if (!$joinModel) {
265: $joinModel = new ActionToRecord;
266: $joinModel->setAttributes (array (
267: 'actionId' => $this->id,
268: 'recordId' => $this->associationId,
269: 'recordType' => $this->associationType,
270: ), false);
271: $success &= $joinModel->save ();
272: }
273: $this->associationId = null;
274: }
275: $this->associationType = self::ASSOCIATION_TYPE_MULTI;
276: $success &= $this->save();
277: return $success;
278: }
279:
280: 281: 282: 283:
284: public function getAttributeLabel ($attribute, $short=false) {
285: $label = '';
286:
287: if ($attribute === 'dueDate') {
288: switch ($this->type) {
289: case 'time':
290: case 'call':
291: if ($short)
292: $label = Yii::t('actions', 'Start');
293: else
294: $label = Yii::t('actions', 'Time Started');
295: break;
296: case 'event':
297: if ($short)
298: $label = Yii::t('actions', 'Start');
299: else
300: $label = Yii::t('actions', 'Start Date');
301: break;
302: default:
303: $label = parent::getAttributeLabel ($attribute);
304: }
305: } else if ($attribute === 'completeDate') {
306: switch ($this->type) {
307: case 'time':
308: case 'call':
309: if ($short)
310: $label = Yii::t('actions', 'End');
311: else
312: $label = Yii::t('actions', 'Time Ended');
313: break;
314: case 'event':
315: if ($short)
316: $label = Yii::t('actions', 'End');
317: else
318: $label = Yii::t('actions', 'End Date');
319: break;
320: default:
321: $label = parent::getAttributeLabel ($attribute);
322: }
323: } else if ($attribute === 'actionDescription') {
324: $label = Yii::t('actions', 'Action Description');
325: } else if ($attribute === 'eventSubtype') {
326: $label = Yii::t('actions', 'Type');
327: } else if ($attribute === 'eventStatus') {
328: $label = Yii::t('actions', 'Status');
329: } else {
330: $label = parent::getAttributeLabel ($attribute);
331: }
332:
333: return $label;
334: }
335:
336: public function attributeNames () {
337: return array_merge (
338: parent::attributeNames (),
339: array_keys ($this->metaDataTemp),
340: array (
341: 'actionDescription',
342: 'notificationTime',
343: 'notificationUsers',
344: )
345: );
346: }
347:
348: public function getAttributes ($names=true) {
349: $attrs = parent::getAttributes ($names);
350: $filter = is_array ($names);
351: if (!$filter || in_array ('actionDescription', $names))
352: $attrs['actionDescription'] = $this->actionDescription;
353:
354:
355:
356:
357: foreach (array_keys ($this->metaDataTemp) as $name) {
358: if (!$filter || in_array ($name, $names))
359: $attrs[$name] = $this->$name;
360: }
361: return $attrs;
362: }
363:
364: public function getAttribute($name, $renderFlag = false, $makeLinks = false){
365: if (in_array ($name, array_keys ($this->metaDataTemp))) {
366: return $this->$name;
367: } elseif ($name === 'actionDescription') {
368: $model = ActionText::model ()->findByAttributes (
369: array (
370: 'actionId' => $this->id
371: ));
372: if ($model) return $model->text;
373: } else {
374: return parent::getAttribute ($name, $renderFlag);
375: }
376: return null;
377: }
378:
379: public function getAssociation () {
380: return self::getAssociationModel($this->associationType, $this->associationId);
381: }
382:
383: 384: 385: 386: 387:
388: public function beforeSave(){
389: if($this->scenario !== 'workflow'){
390: $association = self::getAssociationModel($this->associationType, $this->associationId);
391:
392: if($association === null){
393: $this->associationName = 'None';
394: $this->associationId = 0;
395: }else{
396: if($association->hasAttribute('name'))
397: $this->associationName = $association->name;
398: if($association->asa('X2TimestampBehavior') !== null) {
399: if($association->asa('changelog') !== null
400: && Yii::app()->getSuName() == 'Guest')
401: $association->disableBehavior('changelog');
402: $association->updateLastActivity();
403: $association->enableBehavior('changelog');
404: }
405: }
406:
407: if($this->associationName == 'None' && $this->associationType != 'none')
408: $this->associationName = ucfirst($this->associationType);
409:
410: $this->dueDate = Formatter::parseDateTime($this->dueDate);
411: $this->completeDate = Formatter::parseDateTime($this->completeDate);
412: }
413:
414: $timed = $this->isTimedType;
415:
416: if(empty($timeSpent) && !empty($this->completeDate) && !empty($this->dueDate) && $timed) {
417: $this->timeSpent = $this->completeDate - $this->dueDate;
418: }
419:
420:
421:
422: return parent::beforeSave();
423: }
424:
425: public function beforeDelete() {
426:
427: return parent::beforeDelete();
428: }
429:
430: private function saveMetaData () {
431:
432: if(!($this->actionText instanceof ActionText)){
433: $actionText = new ActionText;
434: $actionText->actionId = $this->id;
435: $actionText->text = $this->actionDescriptionTemp;
436: $actionText->save();
437: }else{
438: if($this->actionText->text != $this->actionDescriptionTemp){
439: $this->actionText->text = $this->actionDescriptionTemp;
440: $this->actionText->save();
441: }
442: }
443:
444:
445: if (!$this->actionMetaData instanceof ActionMetaData) {
446: $metaData = new ActionMetaData;
447: $metaData->actionId = $this->id;
448: } else {
449: $metaData = $this->actionMetaData;
450: }
451: foreach ($this->metaDataTemp as $name => $value) {
452: $metaData->$name = $value;
453: }
454:
455: if (!$metaData->save ()) {
456:
457: }
458: }
459:
460:
461: 462: 463: 464: 465: 466: 467:
468: public function findAllWithoutActionText($condition = '', $params = array()){
469: self::$withActionText = false;
470: $models = $this->findAll($condition, $params);
471: self::$withActionText = true;
472: return $models;
473: }
474:
475: public function afterFind(){
476: if(self::$withActionText && $this->actionText instanceof ActionText){
477: $this->actionDescriptionTemp = $this->actionText->text;
478: }
479: if ($this->actionMetaData instanceof ActionMetaData) {
480: foreach ($this->metaDataTemp as $name => $value) {
481: $this->metaDataTemp[$name] = $this->actionMetaData->$name;
482: }
483: }
484: parent::afterFind();
485: }
486:
487: private $_timerIds;
488: public function setTimerIds ($timers) {
489: $this->_timerIds = $timers;
490: $this->skipActionTimers = true;
491: }
492:
493: public function afterSave(){
494: $this->saveMetaData ();
495:
496: if ($this->reminder) {
497: if (!$this->isNewRecord)
498: $this->deleteOldNotifications ($this->notificationUsers);
499: $this->createNotifications (
500: $this->notificationUsers,
501: $this->dueDate - ($this->notificationTime * 60), 'action_reminder');
502: }
503:
504:
505:
506:
507: parent::afterSave();
508:
509: $association = X2Model::getAssociationModel($this->associationType, $this->associationId);
510: if($this->isNewRecord && $association && $association->hasAttribute('lastActivity')){
511: $association->lastActivity = time();
512: $association->update(array('lastActivity'));
513: X2Flow::trigger('RecordUpdateTrigger', array(
514: 'model' => $association,
515: ));
516: }
517:
518: }
519:
520: public function requiredAssoc($attribute, $params = array()){
521:
522: if(!$this->type) {
523: return !$this->hasErrors();
524: }
525:
526: if($this->associationType !== self::ASSOCIATION_TYPE_MULTI &&
527: (gettype ($this->type) !== 'string' || !preg_match ('/^event/', $this->type))) {
528:
529: if(empty($this->$attribute) || strtolower($this->$attribute) == 'none')
530: $this->addError(
531: $attribute,
532: Yii::t('actions', 'Association is required for actions of type {type}', array (
533: '{type}' => $this->type,
534: )));
535: }
536: return !$this->hasErrors();
537: }
538:
539: 540: 541:
542: public function createEvents ($eventType, $timestamp) {
543: $assignees = $this->getAssignees ();
544: foreach ($assignees as $assignee) {
545: $event = new Events;
546: $event->timestamp = $this->createDate;
547: $event->visibility = $this->visibility;
548: $event->type = $eventType;
549: $event->associationType = 'Actions';
550: $event->associationId = $this->id;
551: if ($eventType === 'record_create') {
552: if (in_array ($this->type, array ('call', 'time', 'note')))
553: $event->subtype = $this->type;
554: $event->user = Yii::app()->user->getName();
555: $event->save();
556: break;
557: } else {
558: $event->user = $assignee;
559: }
560: $event->save();
561: }
562: }
563:
564: public function createCalendarFeedEvent () {
565: $event = new Events;
566: $event->type = 'calendar_event';
567: $event->visibility = $this->visibility;
568: $event->associationType = 'Actions';
569: $event->associationId = $this->id;
570: $event->timestamp = $this->dueDate;
571: $event->save ();
572: }
573:
574: 575: 576: 577:
578: public function createNotifications (
579: $notificationUsers='assigned', $createDate=null, $type='create') {
580:
581: $notifications = array ();
582:
583: if (!$createDate) $createDate = time ();
584:
585: $assignees = array ();
586: switch ($notificationUsers) {
587: case 'me':
588: $assignees = array (Yii::app()->user->getName ());
589: break;
590: case 'assigned':
591: $assignees = $this->getAssignees (true);
592: break;
593: case 'both':
594: $assignees = array_unique (array_merge (
595: $this->getAssignees (true),
596: array (Yii::app()->user->getName ())));
597: break;
598: }
599: foreach ($assignees as $assignee) {
600: $notif = new Notification;
601: $notif->user = $assignee;
602: $notif->createdBy = (Yii::app()->params->noSession) ?
603: 'API' : Yii::app()->user->getName();
604: $notif->createDate = $createDate;
605: $notif->type = $type;
606: $notif->modelType = 'Actions';
607: $notif->modelId = $this->id;
608: if ($notif->save()) {
609: $notifications[] = $notif;
610: } else {
611:
612: }
613: }
614: return $notifications;
615: }
616:
617: 618: 619: 620:
621: public function afterCreate(){
622: if($this->type === 'event') {
623: $this->createCalendarFeedEvent ();
624: }
625: if(empty ($this->type) || in_array($this->type, array('call','time','note'))){
626: $this->createEvents ('record_create', $this->createDate);
627: }
628: if(empty($this->type) && $this->complete !== 'Yes' &&
629: ($this->reminder == 1 || $this->reminder == 'Yes')){
630:
631: $this->createEvents ('action_reminder', $this->dueDate);
632: }
633: if($this->scenario != 'noNotif' &&
634: (Yii::app()->params->noSession ||
635: !$this->isAssignedTo (Yii::app()->user->getName(), true))){
636:
637: $this->createNotifications ();
638: }
639:
640:
641:
642: parent::afterCreate();
643: }
644:
645: 646: 647: 648:
649: public function afterDelete(){
650: X2Model::model('Events')->deleteAllByAttributes(array('associationType' => 'Actions', 'associationId' => $this->id, 'type' => 'action_reminder'));
651: X2Model::model('ActionText')->deleteByPk($this->id);
652:
653: parent::afterDelete();
654: }
655:
656: 657: 658:
659: public function setEventSubtype ($value) {
660: $this->metaDataTemp['eventSubtype'] = $value;
661: }
662:
663: public function setEventStatus ($value) {
664: $this->metaDataTemp['eventStatus'] = $value;
665: }
666:
667:
668:
669: public function setActionDescription($value){
670:
671: $this->actionDescriptionTemp = Fields::getPurifier()->purify($value);
672: }
673:
674: public function getEventSubtype () {
675: return $this->metaDataTemp['eventSubtype'];
676: }
677:
678: public function getEventStatus () {
679: return $this->metaDataTemp['eventStatus'];
680: }
681:
682:
683:
684: public function getActionDescription(){
685:
686: return $this->actionDescriptionTemp;
687: }
688:
689: 690: 691:
692: 693: 694: 695: 696: 697: 698: 699: 700: 701: 702: 703: 704: 705: 706: 707: 708: 709: 710: 711: 712: 713: 714: 715: 716: 717: 718: 719: 720: 721: 722: 723: 724: 725:
726:
727: 728: 729: 730: 731:
732: public function complete($completedBy = null, $notes = null){
733: if($completedBy === null){
734: $completedBy = Yii::app()->user->getName();
735: }
736: if(!is_null($notes)){
737: $this->actionDescription.="\n\n".$notes;
738: }
739:
740: $this->complete = 'Yes';
741: $this->completedBy = $completedBy;
742: $this->completeDate = time();
743:
744: $this->disableBehavior('changelog');
745:
746: if($result = $this->update()){
747:
748: X2Flow::trigger('ActionCompleteTrigger', array(
749: 'model' => $this,
750: 'user' => $completedBy
751: ));
752:
753:
754: X2Model::model('Events')->deleteAllByAttributes(
755: array(
756: 'associationType' => 'Actions',
757: 'associationId' => $this->id,
758: 'type' => 'action_reminder'
759: ), 'timestamp > NOW()');
760:
761: $event = new Events;
762: $event->type = 'action_complete';
763: $event->visibility = $this->visibility;
764: $event->associationType = 'Actions';
765: $event->user = Yii::app()->user->getName();
766: $event->associationId = $this->id;
767:
768:
769: if($event->save() && !Yii::app()->user->checkAccess('ActionsAdminAccess')){
770: $notif = new Notification;
771: $notif->type = 'action_complete';
772: $notif->modelType = 'Actions';
773: $notif->modelId = $this->id;
774: $notif->user = 'admin';
775: $notif->createdBy = $completedBy;
776: $notif->createDate = time();
777: $notif->save();
778: }
779: } else {
780: $this->validate ();
781:
782: }
783: $this->enableBehavior('changelog');
784:
785: return $result;
786: }
787:
788: 789: 790: 791:
792: public function uncomplete(){
793: $this->complete = 'No';
794: $this->completedBy = null;
795: $this->completeDate = null;
796:
797: $this->disableBehavior('changelog');
798:
799: if($result = $this->update()){
800: X2Flow::trigger('ActionUncompleteTrigger', array(
801: 'model' => $this,
802: 'user' => Yii::app()->user->getName()
803: ));
804: }
805: $this->enableBehavior('changelog');
806:
807: return $result;
808: }
809:
810: public function getName(){
811: if(!empty($this->subject)){
812: return $this->subject;
813: }else{
814: if($this->type == 'email'){
815: return Formatter::parseEmail($this->actionDescription);
816: }else{
817: return Formatter::truncateText($this->actionDescription, 40);
818: }
819: }
820: }
821:
822: public function getLink($length = 30, $frame = true){
823: $text = $this->name;
824: if($length && mb_strlen($text, 'UTF-8') > $length)
825: $text = CHtml::encode(trim(mb_substr($text, 0, $length, 'UTF-8')).'...');
826: if($frame){
827: return CHtml::link($text, '#', array('class' => 'action-frame-link', 'data-action-id' => $this->id));
828: }else{
829: return CHtml::link($text, $this->getUrl());
830: }
831: }
832:
833: public function frameLink () {
834: return CHtml::link(
835: $this->actionDescription, '#',
836: array('class' => 'action-frame-link', 'data-action-id' => $this->id));
837: }
838:
839: 840: 841: 842: 843: 844:
845: public function getShortActionText($length = 30, $overflow='...'){
846: $actionText = Yii::app()->db->createCommand()->
847: select('SUBSTR(text, 1,'.$length.') AS text, CHAR_LENGTH(text) AS length')->
848: from('x2_action_text')->
849: where('actionId='.$this->id)->queryRow();
850:
851: if($actionText['length'] > $length)
852: $actionText['text'] .= $overflow;
853:
854: return $actionText['text'];
855:
856: }
857:
858: public function getAssociationLink(){
859: $model = self::getAssociationModel($this->associationType, $this->associationId);
860: if($model !== null)
861: return $model->getLink();
862: return false;
863: }
864:
865: public function getRelevantTimestamp() {
866: switch($this->type) {
867: case 'attachment':
868: $timestamp = $this->completeDate;
869: break;
870: case 'email':
871: case 'emailFrom':
872: case 'email_quote':
873: case 'email_invoice':
874: $timestamp = $this->completeDate;
875: break;
876: case 'emailOpened':
877: case 'emailOpened_quote':
878: case 'email_opened_invoice':
879: $timestamp = $this->completeDate;
880: break;
881: case 'event':
882: $timestamp = $this->completeDate;
883: break;
884: case 'note':
885: $timestamp = $this->completeDate;
886: break;
887: case 'quotesDeleted':
888: case 'quotes':
889: $timestamp = $this->createDate;
890: break;
891: case 'time':
892: $timestamp = $this->createDate;
893: break;
894: case 'webactivity':
895: $timestamp = $this->completeDate;
896: break;
897: case 'workflow':
898: $timestamp = $this->completeDate;
899: break;
900: default:
901: $timestamp = $this->createDate;
902: }
903: return $timestamp;
904: }
905:
906: public static function parseStatus($dueDate, $dateWidth='long', $timeWidth='short'){
907: if(empty($dueDate))
908: return false;
909: if(!is_numeric($dueDate))
910: $dueDate = strtotime($dueDate);
911:
912: $timeLeft = $dueDate - time();
913: if($timeLeft < 0) {
914: return
915: "<span class='overdue'>".
916: Formatter::formatDueDate($dueDate, $dateWidth, $timeWidth).
917: "</span>";
918: } else {
919: return Formatter::formatDueDate($dueDate, $dateWidth, $timeWidth);
920: }
921: }
922:
923: public function formatDueDate () {
924: if (in_array ($this->type, array ('call', 'time', 'event'))) {
925: return Formatter::formatDueDate($this->dueDate);
926: } else {
927: return self::parseStatus ($this->dueDate);
928: }
929: }
930:
931: public static function formatTimeLength($seconds){
932: $seconds = abs($seconds);
933: if($seconds < 60)
934: return Yii::t('app', '{n} second|{n} seconds', $seconds);
935: if($seconds < 3600)
936: return Yii::t('app', '{n} minute|{n} minutes', floor($seconds / 60));
937: if($seconds < 86400)
938: return Yii::t('app', '{n} hour|{n} hours', floor($seconds / 3600));
939: if($seconds < 5184000)
940: return Yii::t('app', '{n} day|{n} days', floor($seconds / 86400));
941: else
942: return Yii::t('app', '{n} month|{n} months', floor($seconds / 2592000));
943: }
944:
945: public static function createCondition($filters){
946: Yii::app()->params->profile->actionFilters = json_encode($filters);
947: Yii::app()->params->profile->update(array('actionFilters'));
948: $criteria = X2Model::model('Actions')->getAccessCriteria();
949: $criteria->addCondition(
950: "(type !='workflow' AND type!='email' AND type!='event' AND type!='emailFrom' AND
951: type!='attachment' AND type!='webactivity' AND type not like 'quotes%' AND
952: type!='emailOpened' AND type!='note' AND type!='call') OR type IS NULL");
953: if(isset($filters['complete'], $filters['assignedTo'], $filters['dateType'],
954: $filters['dateRange'], $filters['order'], $filters['orderType'])){
955:
956: switch($filters['complete']){
957: case "No":
958: $criteria->addCondition("complete='No' OR complete IS NULL");
959: break;
960: case "Yes":
961: $criteria->addCondition("complete='Yes'");
962: break;
963: case 'all':
964: break;
965: }
966: switch($filters['assignedTo']){
967: case 'me':
968: list ($cond, $params) = self::model()->getAssignedToCondition (false);
969: $criteria->addCondition($cond);
970: $criteria->params = array_merge ($criteria->params, $params);
971: break;
972: case 'both':
973: list ($cond, $params) = self::model()->getAssignedToCondition (true);
974: $criteria->addCondition($cond);
975: $criteria->params = array_merge ($criteria->params, $params);
976: break;
977: }
978: switch($filters['dateType']){
979: case 'due':
980: $dateField = 'dueDate';
981: break;
982: case 'create':
983: $dateField = 'createDate';
984: }
985: switch($filters['dateRange']){
986: case 'today':
987: if($dateField == 'dueDate'){
988: $criteria->addCondition("IFNULL(dueDate, createDate) <= ".strtotime('today 11:59 PM'));
989: }else{
990: $criteria->addCondition("$dateField >= ".strtotime('today')." AND $dateField <= ".strtotime('today 11:59 PM'));
991: }
992: break;
993: case 'tomorrow':
994: if($dateField == 'dueDate'){
995: $criteria->addCondition("IFNULL(dueDate, createDate) <= ".strtotime("tomorrow 11:59 PM"));
996: }else{
997: $criteria->addCondition("$dateField >= ".strtotime('tomorrow')." AND $dateField <= ".strtotime("tomorrow 11:59 PM"));
998: }
999: break;
1000: case 'week':
1001: if($dateField == 'dueDate'){
1002: $criteria->addCondition("IFNULL(dueDate, createDate) <= ".strtotime("Sunday 11:59 PM"));
1003: }else{
1004: $criteria->addCondition("$dateField >= ".strtotime('Monday')." AND $dateField <= ".strtotime("Sunday 11:59 PM"));
1005: }
1006: break;
1007: case 'month':
1008: if($dateField == 'dueDate'){
1009: $criteria->addCondition("IFNULL(dueDate, createDate) <= ".strtotime("last day of this month 11:59 PM"));
1010: }else{
1011: $criteria->addCondition("$dateField >= ".strtotime('first day of this month')." AND $dateField <= ".strtotime("last day of this month 11:59 PM"));
1012: }
1013: break;
1014: case 'range':
1015: if(!empty($filters['start']) && !empty($filters['end'])){
1016: if($dateField == 'dueDate'){
1017: $criteria->addCondition("IFNULL(dueDate, createDate) >= ".strtotime($filters['start'])." AND IFNULL(dueDate, createDate) <= ".strtotime($filters['end'].' 11:59 PM'));
1018: }else{
1019: $criteria->addCondition("$dateField >= ".strtotime($filters['start'])." AND $dateField <= ".strtotime($filters['end']));
1020: }
1021: }
1022: break;
1023: }
1024: switch($filters['order']){
1025: case 'due':
1026: $orderField = "IFNULL(dueDate, createDate)";
1027: break;
1028: case 'create':
1029: $orderField = 'createDate';
1030: break;
1031: case 'priority':
1032: $orderField = 'priority';
1033: break;
1034: }
1035: switch($filters['orderType']){
1036: case 'desc':
1037: $criteria->order = "$orderField DESC";
1038: break;
1039: case 'asc':
1040: $criteria->order = "$orderField ASC";
1041: break;
1042: }
1043: }
1044: return $criteria;
1045: }
1046:
1047: public function search($criteria = null, $pageSize=null){
1048: if(!$criteria instanceof CDbCriteria){
1049: $criteria = $this->getAccessCriteria();
1050: $criteria->addCondition(
1051: '(type = "" OR type IS NULL)');
1052: $criteria->addCondition(
1053: "assignedTo REGEXP BINARY :userNameRegex AND complete!='Yes' AND ".
1054: "IFNULL(dueDate, createDate) <= '".strtotime('today 11:59 PM')."'");
1055: $criteria->params = array_merge($criteria->params,array (
1056: ':userNameRegex' => $this->getUserNameRegex ()
1057: ));
1058: }
1059:
1060: return $this->searchBase($criteria, $pageSize);
1061: }
1062:
1063: 1064: 1065:
1066: public function searchIndex($pageSize=null, $uniqueId=null){
1067: $criteria = new CDbCriteria;
1068: $groupIds = User::getMe()->getGroupIds ();
1069: list ($assignedToCondition, $params) = $this->getAssignedToCondition ();
1070: if (Yii::app()->params->profile->showActions === 'overdue') {
1071: $dueDate = time ();
1072: } else {
1073: $dueDate = mktime (24, 0, 0);
1074: }
1075: $parameters = array(
1076: 'condition' =>
1077: $assignedToCondition.
1078: " AND t.dueDate < '".$dueDate."' AND
1079: (t.type=\"\" OR t.type IS NULL)",
1080: 'limit' => ceil(Profile::getResultsPerPage() / 2),
1081: 'params' => $params);
1082: $criteria->scopes = array('findAll' => array($parameters));
1083: return $this->searchBase($criteria, $pageSize);
1084: }
1085:
1086: 1087: 1088:
1089: public function searchAll(){
1090: $criteria = new CDbCriteria;
1091: list ($assignedToCondition, $params) = $this->getAssignedToCondition ();
1092: $condition = $assignedToCondition;
1093: if (Yii::app()->params->profile->showActions === 'overdue') {
1094: $condition = $assignedToCondition.' AND t.dueDate < '.time ();
1095: }
1096: $parameters = array(
1097: "condition" => $condition.' AND (t.type=\'\' OR t.type IS NULL)',
1098: 'limit' => ceil(Profile::getResultsPerPage() / 2),
1099: 'params' => $params);
1100: $criteria->scopes = array('findAll' => array($parameters));
1101: return $this->searchBase($criteria);
1102: }
1103:
1104: 1105: 1106:
1107: public function searchAllGroup(){
1108: $criteria = new CDbCriteria;
1109: if(!Yii::app()->user->checkAccess('ActionsAdmin')){
1110: list ($assignedToCondition, $params) = $this->getAssignedToCondition ();
1111: $criteria->addCondition(
1112: "(t.visibility='1' OR ".$assignedToCondition.")");
1113: $criteria->params = array_merge($criteria->params,$params);
1114: }
1115: if (Yii::app()->params->profile->showActions === 'overdue') {
1116: $criteria->addCondition('t.dueDate < '.time ());
1117: }
1118: $criteria->addCondition('(t.type=\'\' OR t.type IS NULL)');
1119: return $this->searchBase($criteria);
1120: }
1121:
1122: public function searchBase(
1123: $criteria, $pageSize=null, $showHidden=false){
1124:
1125: if ($pageSize === null) {
1126: $pageSize = Profile::getResultsPerPage ();
1127: }
1128:
1129: $this->compareAttributes($criteria);
1130: 1131:
1132: if(!empty($criteria->order)){
1133: $criteria->order = $order = "t.sticky DESC, ".$criteria->order;
1134: }else{
1135: $order = 't.sticky DESC, IF(
1136: t.complete="No", IFNULL(t.dueDate, IFNULL(t.createDate,0)),
1137: GREATEST(t.createDate, IFNULL(t.completeDate,0), IFNULL(t.lastUpdated,0))) DESC';
1138: }
1139:
1140: if ((Yii::app()->controller instanceof ActionsController) &&
1141: Yii::app()->controller->action->id !== 'index') {
1142:
1143: $dataProvider = new SmartActiveDataProvider('Actions', array(
1144: 'sort' => array(
1145: 'defaultOrder' => $order,
1146: ),
1147: 'pagination' => array(
1148: 'pageSize' => $pageSize
1149: ),
1150: 'criteria' => $criteria,
1151: 'uid' => $this->uid,
1152: 'dbPersistentGridSettings' => $this->dbPersistentGridSettings
1153: ));
1154: } else {
1155:
1156:
1157: $dataProvider = new CActiveDataProvider('Actions', array(
1158: 'sort' => array(
1159: 'defaultOrder' => $order,
1160: ),
1161: 'pagination' => array(
1162: 'pageSize' => $pageSize
1163: ),
1164: 'criteria' => $criteria,
1165: ));
1166: }
1167:
1168: return $dataProvider;
1169: }
1170:
1171: 1172: 1173:
1174: public function compareAttributes(&$criteria){
1175: if ($this->asa ('TagBehavior') && $this->asa ('TagBehavior')->getEnabled () &&
1176: $this->tags) {
1177:
1178: $tagCriteria = new CDbCriteria;
1179: $this->compareTags ($tagCriteria);
1180: $criteria->mergeWith ($tagCriteria);
1181: }
1182:
1183: $dbAttributes = array_flip (array_keys ($this->getMetaData ()->columns));
1184: foreach(self::$_fields[$this->tableName()] as &$field){
1185: if(isset ($dbAttributes[$field->fieldName])) {
1186: $this->compareAttribute ($criteria, $field);
1187: }
1188: }
1189: }
1190:
1191: 1192: 1193:
1194: public function syncGoogleCalendar($operation, $ajax=false){
1195: $profiles = $this->getProfilesOfAssignees ();
1196:
1197: foreach($profiles as &$profile){
1198: if($profile !== null){
1199: if($operation === 'create') {
1200:
1201: $profile->syncActionToGoogleCalendar($this, $ajax);
1202: } elseif($operation === 'update') {
1203:
1204: $profile->updateGoogleCalendarEvent($this, $ajax);
1205: } elseif($operation === 'delete') {
1206:
1207: $profile->deleteGoogleCalendarEvent($this, $ajax);
1208: }
1209: }
1210: }
1211: }
1212:
1213: 1214: 1215: 1216:
1217: public function getActionLink ($linkText) {
1218: return CHtml::link(
1219: $linkText,
1220: '#',
1221: array(
1222: 'class' => 'action-frame-link',
1223: 'data-action-id' => $this->id
1224: )
1225: );
1226: }
1227:
1228: 1229: 1230: 1231: 1232: 1233:
1234: public static function changeCompleteState ($operation, $ids) {
1235: $updated = 0;
1236: foreach(self::model()->findAllByPk ($ids) as $action){
1237: if($action === null)
1238: continue;
1239:
1240: if($action->isAssignedTo (Yii::app()->user->getName ()) ||
1241: Yii::app()->params->isAdmin){
1242:
1243: if($operation === 'complete') {
1244: if ($action->complete()) $updated++;
1245: } elseif($operation === 'uncomplete') {
1246: if ($action->uncomplete()) $updated++;
1247: }
1248: }
1249: }
1250: return $updated;
1251: }
1252:
1253: 1254: 1255:
1256: public function getIsTimedType() {
1257: return $this->type == 'time' || $this->type == 'call';
1258: }
1259:
1260:
1261:
1262: 1263: 1264: 1265: 1266:
1267: public function getProfilesOfAssignees () {
1268: $assignees = $this->getAssignees (true);
1269: $profiles = array ();
1270:
1271:
1272: $usernames = array ();
1273:
1274: foreach ($assignees as $assignee) {
1275: $profile = X2Model::model('Profile')->findByAttributes(array (
1276: 'username' => $assignee
1277: ));
1278: if ($profile) {
1279: $profiles[] = $profile;
1280: }
1281: }
1282: return $profiles;
1283: }
1284:
1285: 1286: 1287:
1288: public function getEditableFieldNames ($suppressAttributeLabels=true) {
1289: $editableFieldNames = parent::getEditableFieldNames ($suppressAttributeLabels);
1290: if ($this->scenario === 'X2FlowCreateAction') {
1291: if ($suppressAttributeLabels) {
1292: $editableFieldNames[] = 'type';
1293: } else {
1294: $editableFieldNames['type'] = $this->getAttributeLabel ('type');
1295: }
1296: }
1297: return $editableFieldNames;
1298: }
1299:
1300: public static function getPriorityLabels(){
1301: if(!isset(self::$_priorityLabels)){
1302: self::$_priorityLabels = array(
1303: 1 => Yii::t('actions', 'Low'),
1304: 2 => Yii::t('actions', 'Medium'),
1305: 3 => Yii::t('actions', 'High')
1306: );
1307: }
1308: return self::$_priorityLabels;
1309: }
1310:
1311: 1312: 1313: 1314:
1315: public function getPriorityLabel() {
1316: $priorityLabels = self::getPriorityLabels();
1317: $label = $priorityLabels[1];
1318: if (!empty($this->priority) && array_key_exists($this->priority, $priorityLabels))
1319: $label = $priorityLabels[$this->priority];
1320: return $label;
1321: }
1322:
1323: 1324: 1325: 1326: 1327: 1328: 1329: 1330:
1331: public function renderAttribute(
1332: $fieldName, $makeLinks = true, $textOnly = true, $encode = true){
1333:
1334: $render = function($x)use($encode) {
1335: return $encode ? CHtml::encode($x) : $x;
1336: };
1337:
1338: switch ($fieldName) {
1339: case 'stageNumber':
1340: $workflowStage = $this->workflowStage;
1341: if ($workflowStage)
1342: return $render ($workflowStage->name);
1343: else
1344: return null;
1345: case 'priority':
1346: return $render ($this->getPriorityLabel ());
1347: default:
1348: return parent::renderAttribute($fieldName, $makeLinks, $textOnly, $encode);
1349: }
1350: }
1351:
1352: public function renderInlineViewLink ($text=null) {
1353: switch ($this->type) {
1354: case 'quotes':
1355: $quotePrint = (bool) preg_match('/^\d+$/',$this->actionDescription);
1356: $objectId = $quotePrint ? $this->actionDescription : $this->id;
1357: if (!$text) {
1358: $text = Yii::t('app', '[View quote]');
1359: }
1360: echo CHtml::link(
1361: $text,
1362: 'javascript:void(0);',
1363: array(
1364: 'onclick' => 'return false;',
1365: 'id' => $objectId,
1366: 'class' => $quotePrint ? 'quote-print-frame' : 'quote-frame'
1367: )
1368: );
1369: break;
1370: case 'email':
1371: case 'emailFrom':
1372: case 'email_quote':
1373: case 'email_invoice':
1374: case 'emailOpened':
1375: case 'emailOpened_quote':
1376: case 'emailOpened_invoice':
1377: if (!$text) $text = Yii::t('app', '[View email]');
1378: echo CHtml::link (
1379: $text,
1380: '#',
1381: array(
1382: 'onclick' => 'return false;',
1383: 'id' => $this->id,
1384: 'class' => 'email-frame'
1385: ));
1386: break;
1387: }
1388: }
1389:
1390: 1391: 1392: 1393:
1394: public function renderInput($fieldName, $htmlOptions = array()){
1395: switch ($fieldName) {
1396: case 'color':
1397: $field = $this->getField ($fieldName);
1398: $options = Dropdowns::getItems($field->linkType, null, false);
1399: $enableDropdownLegend = Yii::app()->settings->enableColorDropdownLegend;
1400: if ($enableDropdownLegend) {
1401: $htmlOptions['options'] = array ();
1402: foreach ($options as $value => $label) {
1403: $brightness = X2Color::getColorBrightness ($value);
1404: $fontColor = $brightness > 127.5 ? 'black' : 'white';
1405: $htmlOptions['options'][$value] = array (
1406: 'style' =>
1407: 'background-color: '.$value.';
1408: color: '.$fontColor,
1409: );
1410: }
1411: }
1412: return CHtml::activeDropDownList($this, $field->fieldName, $options, $htmlOptions);
1413: case 'priority':
1414: return CHtml::activeDropdownList($this,'priority',self::getPriorityLabels());
1415: case 'associationType':
1416: return X2Html::activeMultiTypeAutocomplete (
1417: $this, 'associationType', 'associationId',
1418: array ('calendar' => Yii::t('app', 'Select an option')) +
1419: X2Model::getAssociationTypeOptions ());
1420: case 'reminder':
1421: $reminderInput = parent::renderInput (
1422: $fieldName, array (
1423: 'class' => 'reminder-checkbox',
1424: ));
1425: $reminderInput .=
1426: X2Html::openTag ('div', X2Html::mergeHtmlOptions ($htmlOptions, array (
1427: 'class' => 'reminder-config',
1428: ))).
1429: Yii::t(
1430: 'actions',
1431: 'Create a notification reminder for {user} {time} before this {action} '.
1432: 'is due',
1433: array(
1434: '{user}' => CHtml::activeDropDownList(
1435: $this,
1436: 'notificationUsers',
1437: array(
1438: 'me' => Yii::t('actions', 'me'),
1439: 'assigned' => Yii::t('actions', 'the assigned user'),
1440: 'both' => Yii::t('actions', 'me and the assigned user'),
1441: )
1442: ),
1443: '{time}' => CHtml::activeDropDownList(
1444: $this, 'notificationTime',
1445: array(
1446: 1 => Yii::t('actions','1 minute'),
1447: 5 => Yii::t('actions','5 minutes'),
1448: 10 => Yii::t('actions','10 minutes'),
1449: 15 => Yii::t('actions','15 minutes'),
1450: 30 => Yii::t('actions','30 minutes'),
1451: 60 => Yii::t('actions','1 hour'),
1452: 1440 => Yii::t('actions','1 day'),
1453: 10080 => Yii::t('actions','1 week')
1454: )),
1455: '{action}' => lcfirst(Modules::displayName(false, 'Actions')),
1456: )).'</div>';
1457: return $reminderInput;
1458: default:
1459: return parent::renderInput($fieldName, $htmlOptions);
1460: }
1461: }
1462:
1463: private $_reminders;
1464: public function getReminders ($refresh = false) {
1465: if (!isset ($this->_reminders) || $refresh) {
1466: $this->_reminders = X2Model::model('Notification')->findAllByAttributes(array(
1467: 'modelType' => 'Actions',
1468: 'modelId' => $this->id,
1469: 'type' => 'action_reminder'
1470: ));
1471: }
1472: return $this->_reminders;
1473: }
1474:
1475: private $_notificationUsers;
1476: public function setNotificationUsers ($notificationUsers) {
1477: $this->_notificationUsers = $notificationUsers;
1478: }
1479:
1480: public function getNotificationUsers () {
1481: if (!isset ($this->_notificationUsers)) {
1482: $reminders = $this->getReminders ();
1483: if(count($reminders) > 1){
1484: $notificationUsers = 'both';
1485: }else{
1486: $notificationUsers = 'assigned';
1487: }
1488: $this->_notificationUsers = $notificationUsers;
1489: }
1490: return $this->_notificationUsers;
1491: }
1492:
1493: private $_notificationTime;
1494: public function setNotificationTime ($notificationTime) {
1495: $this->_notificationTime = $notificationTime;
1496: }
1497:
1498: public function getNotificationTime () {
1499: if (!isset ($this->_notificationTime)) {
1500: $reminders = $this->getReminders ();
1501: if(count($reminders) > 0){
1502: $notifTime = ($this->dueDate - $reminders[0]->createDate) / 60;
1503: }else{
1504: $notifTime = 15;
1505: }
1506: $this->_notificationTime = $notifTime;
1507: }
1508: return $this->_notificationTime;
1509: }
1510:
1511: 1512: 1513: 1514: 1515:
1516: public function getStaticLinkedModels () {
1517: return array_merge (parent::getStaticLinkedModels (), self::getModuleModelsByName ());
1518: }
1519:
1520: 1521: 1522: 1523: 1524:
1525: private function deleteOldNotifications ($notificationUsers) {
1526: $notifCount = (int) X2Model::model('Notification')->countByAttributes(array(
1527: 'modelType' => 'Actions',
1528: 'modelId' => $this->id,
1529: 'type' => 'action_reminder'
1530: ));
1531: if ($notifCount === 0) return;
1532:
1533: $notifications = X2Model::model('Notification')->findAllByAttributes(array(
1534: 'modelType' => 'Actions',
1535: 'modelId' => $this->id,
1536: 'type' => 'action_reminder'
1537: ));
1538:
1539: foreach($notifications as $notification){
1540: if ($this->isAssignedTo ($notification->user, true) &&
1541: ($notificationUsers == 'assigned' ||
1542: $notificationUsers == 'both')){
1543:
1544: $notification->delete();
1545: }elseif($notification->user == Yii::app()->user->getName() &&
1546: ($notificationUsers == 'me' ||
1547: $notificationUsers == 'both')){
1548:
1549: $notification->delete();
1550: }
1551: }
1552:
1553: }
1554:
1555: }
1556: