Overview

Packages

  • application
    • commands
    • components
      • actions
      • filters
      • leftWidget
      • permissions
      • sortableWidget
      • util
      • webupdater
      • x2flow
        • actions
        • triggers
      • X2GridView
      • X2Settings
    • controllers
    • models
      • embedded
    • modules
      • accounts
        • controllers
        • models
      • actions
        • controllers
        • models
      • calendar
        • controllers
        • models
      • charts
        • models
      • contacts
        • controllers
        • models
      • docs
        • components
        • controllers
        • models
      • groups
        • controllers
        • models
      • marketing
        • components
        • controllers
        • models
      • media
        • controllers
        • models
      • mobile
        • components
      • opportunities
        • controllers
        • models
      • products
        • controllers
        • models
      • quotes
        • controllers
        • models
      • services
        • controllers
        • models
      • template
        • models
      • users
        • controllers
        • models
      • workflow
        • controllers
        • models
      • x2Leads
        • controllers
        • models
  • None
  • system
    • base
    • caching
    • console
    • db
      • ar
      • schema
    • validators
    • web
      • actions
      • auth
      • helpers
      • widgets
        • captcha
        • pagers
  • zii
    • widgets
      • grid

Classes

  • Actions
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: /*****************************************************************************************
   3:  * X2Engine Open Source Edition is a customer relationship management program developed by
   4:  * X2Engine, Inc. Copyright (C) 2011-2016 X2Engine Inc.
   5:  * 
   6:  * This program is free software; you can redistribute it and/or modify it under
   7:  * the terms of the GNU Affero General Public License version 3 as published by the
   8:  * Free Software Foundation with the addition of the following permission added
   9:  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
  10:  * IN WHICH THE COPYRIGHT IS OWNED BY X2ENGINE, X2ENGINE DISCLAIMS THE WARRANTY
  11:  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
  12:  * 
  13:  * This program is distributed in the hope that it will be useful, but WITHOUT
  14:  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  15:  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
  16:  * details.
  17:  * 
  18:  * You should have received a copy of the GNU Affero General Public License along with
  19:  * this program; if not, see http://www.gnu.org/licenses or write to the Free
  20:  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  21:  * 02110-1301 USA.
  22:  * 
  23:  * You can contact X2Engine, Inc. P.O. Box 66752, Scotts Valley,
  24:  * California 95067, USA. or at email address contact@x2engine.com.
  25:  * 
  26:  * The interactive user interfaces in modified source and object code versions
  27:  * of this program must display Appropriate Legal Notices, as required under
  28:  * Section 5 of the GNU Affero General Public License version 3.
  29:  * 
  30:  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
  31:  * these Appropriate Legal Notices must retain the display of the "Powered by
  32:  * X2Engine" logo. If the display of the logo is not reasonably feasible for
  33:  * technical reasons, the Appropriate Legal Notices must display the words
  34:  * "Powered by X2Engine".
  35:  *****************************************************************************************/
  36: 
  37: Yii::import('application.models.X2Model');
  38: 
  39: /**
  40:  * This is the model class for table "x2_actions".
  41:  * @package application.modules.actions.models
  42:  */
  43: class Actions extends X2Model {
  44: 
  45:     /**
  46:      * If we want this to be user configurable, then the height of the list view items container 
  47:      * needs to be set dynamically. Otherwise, infinity scrolling will break.
  48:      */
  49:     const ACTION_INDEX_PAGE_SIZE = 20;
  50: 
  51:     const COLORS_DROPDOWN_ID = 123;
  52: 
  53:     /**
  54:      * Setting associationType to this implies that the action is associated via the 
  55:      * x2_action_to_record table
  56:      */
  57:     const ASSOCIATION_TYPE_MULTI = '__multiple__';
  58: 
  59:     public $skipActionTimers = false;
  60: 
  61:     public $supportsWorkflow = false;
  62: 
  63:     /**
  64:      * Types of actions that should be treated as emails
  65:      * @var type
  66:      */
  67:     public static $emailTypes = array(
  68:         'email', 'emailFrom','emailOpened','email_invoice', 'email_quote');
  69: 
  70:     public $verifyCode; // CAPTCHA for guests using the publisher
  71:     public $actionDescriptionTemp = ''; // Easy way to get around action text records
  72: 
  73:     private $metaDataTemp = array (
  74:         'eventSubtype' => null,
  75:         'eventStatus' => null,
  76:         
  77:     );
  78: 
  79:     private static $_priorityLabels;
  80: 
  81:     /* static variable to allow calling findAll without actionText */ 
  82:     private static $withActionText = true;
  83: 
  84:     /**
  85:      * Add note to model 
  86:      * @param X2Model $model model to which note should be added
  87:      * @param string $note
  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:      * Get names of CFormModel classes associated with action subtypes 
 109:      * @return array
 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:      * Returns the static model of the specified AR class.
 129:      * @return Actions the static model class
 130:      */
 131:     public static function model($className = __CLASS__){
 132:         return parent::model($className);
 133:     }
 134: 
 135:     /**
 136:      * @return string the associated database table name
 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:             //'changelog' => array('class' => 'X2ChangeLogBehavior'),
 158:         );
 159:     }
 160: 
 161:     /**
 162:      * @return array validation rules for model attributes.
 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:      * @return array relational rules.
 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:             //'assignee' => array(self::BELONGS_TO,'User',array('assignedTo'=>'username')),
 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:      * Associate this action with a record (using join table)
 211:      * @param X2Model $model 
 212:      * @return mixed false if model couldn't be saved, -1 if association already exists, true
 213:      *  if successful
 214:      * @throws CException if this action has an invalid association type
 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:      * Retrieve the associated models
 238:      * @return array of associated models, indexed by model type
 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:      * Convert an Action from a single association to multiassociation
 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:      * Returns action type specific attribute labels
 282:      * @return String
 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: //        if (!$filter || in_array ('notificationUsers', $names))
 354: //            $attrs['notificationUsers'] = $this->notificationUsers;
 355: //        if (!$filter || in_array ('notificationTime', $names))
 356: //            $attrs['notificationTime'] = $this->notificationTime;
 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:      * Fixes up record association, parses dates (since this doesn't use 
 385:      * {@link X2Model::setX2Fields()})
 386:      * @return boolean whether or not to save
 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:         // Whether this is a "timed" action record:
 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:         // No action text exists for this yet
 432:         if(!($this->actionText instanceof ActionText)){
 433:             $actionText = new ActionText; // Create new oen
 434:             $actionText->actionId = $this->id;
 435:             $actionText->text = $this->actionDescriptionTemp; // A magic setter sets actionDescriptionTemp value
 436:             $actionText->save();
 437:         }else{ // We have an action text
 438:             if($this->actionText->text != $this->actionDescriptionTemp){ // Only update if different
 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:             //AuxLib::debugLogR ($metaData->getErrors ());
 457:         }
 458:     }
 459: 
 460: 
 461:     /** 
 462:     * Modified findAll function that doesn't attach actionText. See {@link X2Model::findAll}.
 463:     * @param mixed $condition query condition or criteria
 464:     * @param array $params parameters to be bound to an SQL statement.
 465:     * @return CActiveRecord[] list of active records satisfying the specified condition. 
 466:     * An empty array is returned if none is found.
 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:         // all action types but events and empty require this attribute
 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:      * Creates an event for each assignee 
 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; // only create one record, not one for each assignee
 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:      * Creates a notification for each assignee 
 576:      * @return array the notifications created by this method
 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:                 //AuxLib::debugLogR ($notif->getErrors ());
 612:             }
 613:         }
 614:         return $notifications;
 615:     }
 616: 
 617:     /**
 618:      * Creates an action reminder event.
 619:      * Fires the onAfterCreate event in {@link X2Model::afterCreate}
 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:      * Deletes the action reminder event, if any
 647:      * Fires the onAfterDelete event in {@link X2Model::afterDelete}
 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:      * Sets action subtype for actions of type event 
 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:         // Magic setter stores value in actionDescriptionTemp until saved
 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:         // Magic getter only ever refers to actionDescriptionTemp
 686:         return $this->actionDescriptionTemp;
 687:     }
 688: 
 689:     /**
 690:      * Sends email reminders to all assignees
 691:      */
 692:     /*public function sendEmailRemindersToAssignees () {
 693:         $emails = User::getEmails();
 694: 
 695:         $assignees = $this->getAssignees (true);
 696: 
 697:         foreach ($assignees as $assignee) {
 698: 
 699:             if($this->associationId != 0){
 700:                 $contact = X2Model::model('Contacts')->findByPk($this->associationId);
 701:                 $name = $contact->firstName.' '.$contact->lastName;
 702:             } else
 703:                 $name = Yii::t('actions', 'No one');
 704:             if(isset($emails[$assignee])){
 705:                 $email = $emails[$assignee];
 706:             }else{
 707:                 continue;
 708:             }
 709:             if(isset($this->type))
 710:                 $type = $this->type;
 711:             else
 712:                 $type = Yii::t('actions', 'Not specified');
 713:     
 714:             $subject = Yii::t('actions', 'Action Reminder:');
 715:             $body = Yii::t('actions', "Reminder, the following action is due today: \n Description: {description}\n Type: {type}.\n Associations: {name}.\nLink to the action: ", array('{description}' => $this->actionDescription, '{type}' => $type, '{name}' => $name))
 716:                     .Yii::app()->controller->createAbsoluteUrl('/actions/actions/view',array('id'=>$this->id));
 717:             $headers = 'From: '.Yii::app()->params['adminEmail'];
 718:     
 719:             if($this->associationType != 'none')
 720:                 $body.="\n\n".Yii::t('actions', 'Link to the {type}', array('{type}' => ucfirst($this->associationType))).': '.Yii::app()->controller->createAbsoluteUrl(str_repeat('/'.$this->associationType,2).'/view',array('id'=>$this->associationId));
 721:             $body.="\n\n".Yii::t('actions', 'Powered by ').'<a href=http://x2engine.com>X2Engine</a>';
 722:     
 723:             mail($email, $subject, $body, $headers);
 724:         }
 725:     }*/
 726: 
 727:     /**
 728:      * Marks the action complete and updates the record.
 729:      * @param string $completedBy the user completing the action (defaults to currently logged in user)
 730:      * @return boolean whether or not the action updated successfully
 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:             // delete the action reminder event
 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:             // notify the admin
 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:             //AuxLib::debugLogR ($this->getErrors ());
 782:         }
 783:         $this->enableBehavior('changelog');
 784: 
 785:         return $result;
 786:     }
 787: 
 788:     /**
 789:      * Marks the action incomplete and updates the record.
 790:      * @return boolean whether or not the action updated successfully
 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:      * Queries the database for the first characters of an action description
 841:      * @param int $length length of string to retrieve
 842:      * @param string $overflow string to append to text if it overflows
 843:      * @return string
 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)) // there is no due date
 908:             return false;
 909:         if(!is_numeric($dueDate))
 910:             $dueDate = strtotime($dueDate); // make sure $date is a proper timestamp
 911: 
 912:         $timeLeft = $dueDate - time(); // calculate how long till due date
 913:         if($timeLeft < 0) {
 914:             return 
 915:                 "<span class='overdue'>".
 916:                     Formatter::formatDueDate($dueDate, $dateWidth, $timeWidth).
 917:                 "</span>"; // overdue by X hours/etc
 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); // less than 1 min
 935:         if($seconds < 3600)
 936:             return Yii::t('app', '{n} minute|{n} minutes', floor($seconds / 60)); // minutes (less than an hour)
 937:         if($seconds < 86400)
 938:             return Yii::t('app', '{n} hour|{n} hours', floor($seconds / 3600)); // hours (less than a day)
 939:         if($seconds < 5184000)
 940:             return Yii::t('app', '{n} day|{n} days', floor($seconds / 86400)); // days (less than 60 days)
 941:         else
 942:             return Yii::t('app', '{n} month|{n} months', floor($seconds / 2592000)); // months (more than 90 days)
 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:      * Today's Actions 
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:      * All My Actions
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:      * Everyone's Actions 
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:         /*$criteria->with = 'actionText';
1131:         $criteria->compare('actionText.text', $this->actionDescriptionTemp, true);*/
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:             // for actions index, use CActiveDataProvider since SmartActiveDataProvider is 
1156:             // incompatible with IasPager
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:      * Override parent method to exclude actionDescription
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:      * TODO: unit test 
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:                     // create action to Google Calendar
1201:                     $profile->syncActionToGoogleCalendar($this, $ajax); 
1202:                 } elseif($operation === 'update') {
1203:                     // update action to Google Calendar
1204:                     $profile->updateGoogleCalendarEvent($this, $ajax); 
1205:                 } elseif($operation === 'delete') {
1206:                     // delete action in Google Calendar
1207:                     $profile->deleteGoogleCalendarEvent($this, $ajax); 
1208:                 }
1209:             }
1210:         }
1211:     }
1212: 
1213:     /**
1214:      * Returns a link which opens an action view dialog. Event bound in actionFrames.js. 
1215:      * @param string $linkText The text to display in the <a> tag.
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:      * Completes/uncompletes set of actions 
1230:      * @param string $operation <'complete' | 'uncomplete'>
1231:      * @param array $ids
1232:      * @return int $updated number of actions updated successfully
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){ // make sure current user can edit this action
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:      * Returns whether this is the type of action that can be time-tracked
1255:      */
1256:     public function getIsTimedType() {
1257:         return $this->type == 'time' || $this->type == 'call';
1258:     }
1259: 
1260:       
1261: 
1262:     /**
1263:      * @return array all profiles of assignees. For assignees which are groups, all profiles of
1264:      *  users in those groups are returned. If an assignee is included more than once,
1265:      *  duplicate profiles are removed.
1266:      */
1267:     public function getProfilesOfAssignees () {
1268:         $assignees = $this->getAssignees (true);  
1269:         $profiles = array ();
1270: 
1271:         // prevent duplicate entries in $profiles by keeping track of included usernames
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:      * Override parent method so that action type can be set from X2Flow create action 
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:      * Retrieve the priority label string, or return the default priority ("Low")
1313:      * @return string Priority label
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:      * Special override that prints priority accordingly
1325:      * @param type $fieldName
1326:      * @param type $makeLinks
1327:      * @param type $textOnly
1328:      * @param type $encode
1329:      * @return type
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:      * @param type $fieldName
1392:      * @param type $htmlOptions
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:      * Overrides parent method to add models which can be linked through the association[id|type]
1513:      * fields.
1514:      * @return array static linked models indexed by link field name
1515:      */
1516:     public function getStaticLinkedModels () {
1517:         return array_merge (parent::getStaticLinkedModels (), self::getModuleModelsByName ());
1518:     }
1519: 
1520:     /**
1521:      * Deletes duplicate notifications. Meant to be called before the creation of new notifications
1522:      * @param string $notificationUsers assignee of the newly created notifications
1523:      * TODO: unit test
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: 
X2CRM Documentation API documentation generated by ApiGen 2.8.0