1: <?php
2:
3: /*****************************************************************************************
4: * X2Engine Open Source Edition is a customer relationship management program developed by
5: * X2Engine, Inc. Copyright (C) 2011-2016 X2Engine Inc.
6: *
7: * This program is free software; you can redistribute it and/or modify it under
8: * the terms of the GNU Affero General Public License version 3 as published by the
9: * Free Software Foundation with the addition of the following permission added
10: * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11: * IN WHICH THE COPYRIGHT IS OWNED BY X2ENGINE, X2ENGINE DISCLAIMS THE WARRANTY
12: * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13: *
14: * This program is distributed in the hope that it will be useful, but WITHOUT
15: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16: * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
17: * details.
18: *
19: * You should have received a copy of the GNU Affero General Public License along with
20: * this program; if not, see http://www.gnu.org/licenses or write to the Free
21: * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22: * 02110-1301 USA.
23: *
24: * You can contact X2Engine, Inc. P.O. Box 66752, Scotts Valley,
25: * California 95067, USA. or at email address contact@x2engine.com.
26: *
27: * The interactive user interfaces in modified source and object code versions
28: * of this program must display Appropriate Legal Notices, as required under
29: * Section 5 of the GNU Affero General Public License version 3.
30: *
31: * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32: * these Appropriate Legal Notices must retain the display of the "Powered by
33: * X2Engine" logo. If the display of the logo is not reasonably feasible for
34: * technical reasons, the Appropriate Legal Notices must display the words
35: * "Powered by X2Engine".
36: *****************************************************************************************/
37:
38: /**
39: * @package application.modules.actions.controllers
40: */
41: class ActionsController extends x2base {
42:
43: public $modelClass = 'Actions';
44: public $showActions = null;
45:
46: public function behaviors() {
47: return array_merge(parent::behaviors(), array(
48: 'ActionsQuickCreateRelationshipBehavior' => array(
49: 'class' => 'ActionsQuickCreateRelationshipBehavior',
50: 'attributesOfNewRecordToUpdate' => array(
51: )
52: ),
53: ));
54: }
55:
56: /**
57: * Specifies the access control rules.
58: * This method is used by the 'accessControl' filter.
59: * @return array access control rules
60: */
61: public function accessRules(){
62: return array(
63: array('allow', // allow all users to perform 'index' and 'view' actions
64: 'actions' => array('invalid', 'sendReminder', 'emailOpened'),
65: 'users' => array('*'),
66: ),
67: array('allow', // allow authenticated user to perform 'create' and 'update' actions
68: 'actions' => array('index', 'view', 'create', 'createSplash', 'createInline', 'viewGroup', 'complete', //quickCreate
69: 'completeRedirect', 'update', 'quickUpdate', 'saveShowActions', 'viewAll', 'search', 'completeNew', 'parseType', 'uncomplete', 'uncompleteRedirect', 'delete', 'shareAction', 'inlineEmail', 'publisherCreate','saveShowActions', 'copyEvent'),
70: 'users' => array('@'),
71: ),
72: array('allow', // allow admin user to perform 'admin' and 'delete' actions
73: 'actions' => array('admin', 'testScalability'),
74: 'users' => array('admin'),
75: ),
76: array('deny', // deny all users
77: 'users' => array('*'),
78: ),
79: );
80: }
81:
82: public function actions(){
83: return array_merge(parent::actions(), array(
84: 'captcha' => array(
85: 'class' => 'CCaptchaAction',
86: 'backColor' => 0xeeeeee,
87: ),
88: 'timerControl' => array(
89: 'class' => 'application.modules.actions.components.TimerControlAction',
90: ),
91: ));
92: }
93: public function actionSaveShowActions(){
94: if(isset($_POST['ShowActions'])){
95: $profile = Profile::model()->findByPk(Yii::app()->user->id);
96: $profile->showActions = $_POST['ShowActions'];
97: $profile->update();
98: }
99: }
100:
101: /**
102: * Displays a particular model.
103: * @param integer $id the ID of the model to be displayed
104: */
105: public function actionView($id){
106: $action = CActiveRecord::model('Actions')->findByPk($id);
107:
108: if($action === null)
109: $this->redirect('index');
110:
111: $users = User::getNames();
112: $association = $this->getAssociation($action->associationType, $action->associationId);
113:
114: if($this->checkPermissions($action, 'view')){
115:
116: X2Flow::trigger('RecordViewTrigger', array('model' => $action));
117:
118: User::addRecentItem('t', $id, Yii::app()->user->getId()); //add action to user's recent item list
119: $this->render('view', array(
120: 'model' => $this->loadModel($id),
121: 'associationModel' => $association,
122: 'users' => $users,
123: ));
124: } else
125: $this->redirect('index');
126: }
127:
128: public function actionViewEmail($id){
129: $this->redirectOnNullModel = false;
130: $action = $this->loadModel($id);
131: if(!Yii::app()->user->isGuest ||
132: Yii::app()->user->checkAccess(ucfirst($action->associationType).'View')){
133:
134: header('Content-Type: text/html; charset=utf-8');
135: if(!Yii::app()->user->isGuest){
136: echo preg_replace(
137: '/<\!--BeginOpenedEmail-->(.*?)<\!--EndOpenedEmail-->/s', '',
138: $action->actionDescription);
139: }else{
140: // Strip out the action header since it's being viewed directly:
141: $actionHeaderPattern = InlineEmail::insertedPattern('ah', '(.*)', 1, 'mis');
142: if(!preg_match($actionHeaderPattern, $action->actionDescription, $matches)){
143: // Legacy action header
144: echo preg_replace('/<b>(.*?)<\/b>(.*)/mis', '', $action->actionDescription);
145: }else{
146: // Current action header
147: echo preg_replace($actionHeaderPattern, '', $action->actionDescription);
148: }
149: }
150: }
151: }
152:
153: public function actionViewAction($id, $publisher = false){
154: $this->redirectOnNullModel = false;
155: $this->throwOnNullModel = false;
156: $model = $this->loadModel($id);
157: if(isset($model)){
158: if(in_array($model->type, Actions::$emailTypes)){
159: $this->actionViewEmail($id);
160: return;
161: }
162: X2Flow::trigger('RecordViewTrigger', array('model' => $model));
163: $this->renderPartial('_viewFrame', array(
164: 'model' => $model,
165: 'publisher' => $publisher,
166: ));
167: }else{
168: echo "<b>Error: 404</b><br><br>Unable to find the requested action.";
169: }
170: }
171:
172: public function actionShareAction($id){
173:
174: $model = $this->loadModel($id);
175: $body = "\n\n\n\n".Yii::t('actions', "Reminder, the following action is due")." ".Formatter::formatLongDateTime($model->dueDate).":<br />
176: <br />".Yii::t('actions', 'Description').": $model->actionDescription
177: <br />".Yii::t('actions', 'Type').": $model->type
178: <br />".Yii::t('actions', 'Associations').": ".$model->associationName."
179: <br />".Yii::t('actions', 'Link to the action').": ".CHtml::link('Link', 'http://'.Yii::app()->request->getServerName().$this->createUrl('/actions/'.$model->id));
180: $body = trim($body);
181:
182: $errors = array();
183: $status = array();
184: $email = array();
185: if(isset($_POST['email'], $_POST['body'])){
186:
187: $subject = Yii::t('actions', "Reminder, the following action is due")." ".date("Y-m-d", $model->dueDate);
188: $email['to'] = $this->parseEmailTo($this->decodeQuotes($_POST['email']));
189: $body = $_POST['body'];
190: // if(empty($email) || !preg_match("/[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}/",$email))
191: if($email['to'] === false)
192: $errors[] = 'email';
193: if(empty($body))
194: $errors[] = 'body';
195:
196: if(empty($errors))
197: $status = $this->sendUserEmail($email, $subject, $body);
198:
199: if(array_search('200', $status)){
200: $this->redirect(array('view', 'id' => $model->id));
201: return;
202: }
203: if($email['to'] === false)
204: $email = $_POST['email'];
205: else
206: $email = $this->mailingListToString($email['to']);
207: }
208: $this->render('shareAction', array(
209: 'model' => $model,
210: 'body' => $body,
211: 'email' => $email,
212: 'status' => $status,
213: 'errors' => $errors
214: ));
215: }
216:
217: /*public function actionSendReminder(){
218:
219: $dataProvider = new CActiveDataProvider('Actions', array(
220: 'criteria' => array(
221: 'condition' => '(dueDate<"'.mktime(23, 59, 59).'" AND dueDate>"'.mktime(0, 0, 0).'" AND complete="No")',
222: )));
223:
224: $actionArray = $dataProvider->getData();
225:
226: foreach($actionArray as $action){
227: if($action->reminder == 1){
228: $action->sendEmailRemindersToAssignees ();
229: }
230: }
231: }*/
232:
233: public function create($model, $oldAttributes, $api){
234: if($api == 0){
235: parent::create($model, $oldAttributes, $api);
236: }else
237: return parent::create($model, $oldAttributes, $api);
238: }
239:
240: /**
241: * Creates a new model.
242: * If creation is successful, the browser will be redirected to the 'view' page.
243: */
244: public function actionCreate(){
245: if ((Yii::app()->user->isGuest &&
246: !Yii::app()->user->checkAccess($_POST['Actions']['associationType'].'View'))) {
247:
248: $this->denied ();
249: }
250:
251: $formTypes = Actions::getFormTypes ();
252: foreach ($formTypes as $type) { // determine which kind of action we're creating
253: if (isset ($_POST[$type])) {
254: $post = $_POST[$type];
255: $modelType = $type;
256: break;
257: }
258: }
259: if (!isset ($modelType) && isset ($_POST['actionType']) &&
260: in_array ($_POST['actionType'], $formTypes)) {
261:
262: $modelType = $_POST['actionType'];
263: } elseif (!isset ($modelType)) {
264: $modelType = 'Actions';
265: }
266: $model = new $modelType;
267:
268: if (isset ($post)){
269: if ($model instanceof ActionFormModelBase) {
270: $model->setAttributes ($post);
271:
272: if ($model->validate ()) {
273: $model = $model->getAction (); // convert to active record
274: }
275: } else { // ($model instanceof Actions)
276: $model->setX2Fields ($post);
277: }
278:
279: if (!$model->hasErrors () && isset($_POST['x2ajax'])) {
280: $this->quickCreate($model);
281: $model->syncGoogleCalendar('create');
282: } elseif(!$model->hasErrors () && $model->save()){
283: $model->syncGoogleCalendar('create');
284: $this->redirect(array('index'));
285: }
286: }
287: if(empty($model->assignedTo)){
288: $model->assignedTo = Yii::app()->user->getName();
289: }
290:
291: if (isset($_POST['x2ajax'])) {
292: // allows form to be refreshed
293: if (!$model->hasErrors ()) $model = new $modelType;
294: $this->renderInlineForm ($model);
295: } else {
296: $this->render('create', array(
297: 'model' => $model,
298: ));
299: }
300: }
301:
302: public function actionPublisherCreate(){
303: if(isset($_POST['SelectedTab'], $_POST['Actions']) &&
304: (!Yii::app()->user->isGuest ||
305: Yii::app()->user->checkAccess($_POST['Actions']['associationType'].'View'))) {
306:
307: Yii::app()->clientScript->scriptMap['*.css'] = false;
308:
309: // // if association name is sent without id, try to lookup the record by name and type
310: // if (isset ($_POST['calendarEventTab']) && $_POST['calendarEventTab'] &&
311: // isset ($_POST['Actions']['associationName']) &&
312: // empty ($_POST['Actions']['associationId'])) {
313: //
314: // $associatedModel = X2Model::getModelOfTypeWithName (
315: // $_POST['Actions']['associationType'], $_POST['Actions']['associationName']);
316: // if ($associatedModel) {
317: // $_POST['Actions']['associationId'] = $associatedModel->id;
318: // } else {
319: // echo CJSON::encode (
320: // array ('error' => Yii::t('actions', 'Invalid association name')));
321: // Yii::app()->end ();
322: // }
323: // }
324: //
325: // if(!Yii::app()->user->isGuest){
326: // $model = new Actions;
327: // }else{
328: // $model = new Actions('guestCreate');
329: // $model->verifyCode = $_POST['Actions']['verifyCode'];
330: // }
331: // $model->setX2Fields($_POST['Actions']);
332: // // format dates,
333: // if (isset ($_POST[get_class($model)]['dueDate'])) {
334: // $model->dueDate = Formatter::parseDateTime($_POST[get_class($model)]['dueDate']);
335: // }
336:
337: if($_POST['SelectedTab'] == 'new-event' ||
338: $_POST['SelectedTab'] == 'new-small-calendar-event'){
339:
340: $model->disableBehavior('changelog');
341: $event = new Events;
342: $event->type = 'calendar_event';
343: $event->visibility = $model->visibility;
344: $event->associationType = 'Actions';
345: $event->timestamp = $model->dueDate;
346: $model->type = 'event';
347: if($model->completeDate){
348: $model->completeDate = Formatter::parseDateTime($model->completeDate);
349: }else{
350: $model->completeDate = $model->dueDate;
351: }
352: }
353:
354: // format association
355: if($model->associationId == '')
356: $model->associationId = 0;
357:
358: //$association = $this->getAssociation($model->associationType, $model->associationId);
359:
360: // if($association){
361: //
362: // $model->associationName = $association->name;
363: // if($association->hasAttribute('lastActivity')){
364: // $association->lastActivity = time();
365: // $association->update(array('lastActivity'));
366: // X2Flow::trigger('RecordUpdateTrigger', array(
367: // 'model' => $association,
368: // ));
369: // }
370: // } else
371: // $model->associationName = 'none';
372: //
373: // if($model->associationName == 'None' && $model->associationType != 'none')
374: // $model->associationName = ucfirst($model->associationType);
375:
376: // if(in_array($_POST['SelectedTab'],array('log-a-call','new-comment','log-time-spent'))){
377: // // Set the complete date accordingly:
378: // if(!empty($_POST[get_class($model)]['completeDate'])) {
379: // $model->completeDate = Formatter::parseDateTime(
380: // $_POST[get_class($model)]['completeDate']);
381: // }
382: // foreach(array('dueDate','completeDate') as $attr)
383: // if(empty($model->$attr))
384: // $model->$attr = time();
385: // if($model->dueDate > $model->completeDate) {
386: // // User specified a negative time range! Let's say that the
387: // // starting time is equal to when it ended (which is earlier)
388: // $model->dueDate = $model->completeDate;
389: // }
390: // $model->complete = 'Yes';
391: // $model->visibility = '1';
392: // $model->assignedTo = Yii::app()->user->getName();
393: // $model->completedBy = Yii::app()->user->getName();
394: //// if($_POST['SelectedTab'] == 'log-a-call') {
395: //// $model->type = 'call';
396: //// } elseif($_POST['SelectedTab'] == 'log-time-spent') {
397: //// $model->type = 'time';
398: ////
399: //// } else {
400: //// $model->type = 'note';
401: //// }
402: // }
403: // if(in_array($model->type, array('call','time','note'))){
404: // $event = new Events;
405: // $event->associationType = 'Actions';
406: // $event->type = 'record_create';
407: // $event->user = Yii::app()->user->getName();
408: // $event->visibility = $model->visibility;
409: // $event->subtype = $model->type;
410: // }
411: // // save model
412: // $model->createDate = time();
413: //
414: // if(!empty($model->type))
415: // $model->disableBehavior('changelog');
416: //
417: //
418: if($model->save()){ // action saved to database *
419: // if(isset($_POST['Actions']['reminder']) && $_POST['Actions']['reminder']){
420: // $model->createNotifications(
421: // $_POST['notificationUsers'],
422: // $model->dueDate - ($_POST['notificationTime'] * 60),
423: // 'action_reminder');
424: // }
425:
426: // X2Model::updateTimerTotals(
427: // $model->associationId,X2Model::getModelName($model->associationType));
428:
429: if(isset($event)){
430: $event->associationId = $model->id;
431: $event->save();
432: }
433: //$model->syncGoogleCalendar('create', true);
434: }else{
435: if($model->hasErrors('verifyCode')){
436: echo CJSON::encode (array ('error' => $model->getError('verifyCode')));
437: Yii::app()->end ();
438: }
439: }
440: echo CJSON::encode (array ('success'));
441: Yii::app()->end ();
442: } else {
443: throw new CHttpException (400, Yii::t('app', 'Bad request'));
444: }
445: }
446:
447: /**
448: * Create a menu for Actions
449: * @param array Menu options to remove
450: * @param X2Model Model object passed to the view
451: * @param array Additional menu parameters
452: */
453: public function insertMenu($selectOptions = array(), $model = null, $menuParams = null) {
454: $Action = Modules::displayName(false);
455: $Actions = Modules::displayName();
456: $modelId = isset($model) ? $model->id : 0;
457:
458: /**
459: * To show all options:
460: * $menuOptions = array(
461: * 'list', 'todays', 'my', 'everyones', 'create', 'view', 'edit', 'share',
462: * 'delete', 'import', 'export',
463: * );
464: */
465:
466: $menuItems = array(
467: array(
468: 'name'=>'list',
469: 'label'=>Yii::t('actions','{module} List', array(
470: '{module}' => Modules::displayName(false),
471: )),
472: 'url'=>array('index'),
473: ),
474: array(
475: 'name'=>'todays',
476: 'label'=>Yii::t('actions','Today\'s {module}', array(
477: '{module}' => $Actions,
478: )),
479: 'url'=>array('index'),
480: ),
481: array(
482: 'name'=>'my',
483: 'label'=>Yii::t('actions','All My {module}', array(
484: '{module}' => $Actions,
485: )),
486: 'url'=>array('viewAll')
487: ),
488: array(
489: 'name'=>'everyones',
490: 'label'=>Yii::t('actions','Everyone\'s {module}', array(
491: '{module}' => $Actions,
492: )),
493: 'url'=>array('viewGroup')
494: ),
495: array(
496: 'name'=>'create',
497: 'label'=>Yii::t('actions','Create {module}', array(
498: '{module}' => $Action,
499: )),
500: 'url'=>array('create','param'=>Yii::app()->user->getName().";none:0")
501: ),
502: array(
503: 'name'=>'view',
504: 'label'=>Yii::t('actions','View'),
505: 'url'=>array('view', 'id'=>$modelId),
506: ),
507: array(
508: 'name'=>'edit',
509: 'label'=>Yii::t('actions','Edit {module}', array(
510: '{module}' => $Action,
511: )),
512: 'url'=>array('update', 'id'=>$modelId)
513: ),
514: array(
515: 'name'=>'share',
516: 'label'=>Yii::t('contacts','Share {module}', array(
517: '{module}' => $Action,
518: )),
519: 'url'=>array('shareAction','id'=>$modelId)
520: ),
521: array(
522: 'name'=>'delete',
523: 'label'=>Yii::t('actions','Delete {module}', array(
524: '{module}' => $Action,
525: )),
526: 'url'=>'#',
527: 'linkOptions'=>array(
528: 'submit'=>array('delete','id'=>$modelId),
529: 'confirm'=>'Are you sure you want to delete this item?')
530: ),
531: array(
532: 'name'=>'import',
533: 'label'=>Yii::t('actions', 'Import {module}', array(
534: '{module}' => $Actions,
535: )),
536: 'url'=>array('admin/importModels', 'model'=>'Actions'),
537: ),
538: array(
539: 'name'=>'export',
540: 'label'=>Yii::t('actions', 'Export {module}', array(
541: '{module}' => $Actions,
542: )),
543: 'url'=>array('admin/exportModels', 'model'=>'Actions'),
544: ),
545: );
546:
547: $this->prepareMenu($menuItems, $selectOptions);
548: $this->actionMenu = $this->formatMenu($menuItems, $menuParams);
549: }
550:
551: public function update($model, $oldAttributes, $api){
552:
553: // now in Actions::beforeSave()
554: /* $model->dueDate = Formatter::parseDateTime($model->dueDate);
555:
556: if($model->completeDate)
557: $model->completeDate = Formatter::parseDateTime($model->completeDate);
558:
559: $association = $this->getAssociation($model->associationType,$model->associationId);
560:
561: if($association != null) {
562: $model->associationName = $association->name;
563: } else {
564: $model->associationName = 'None';
565: $model->associationId = 0;
566: } */
567:
568: // now in Actions::synchGoogleCalendar()
569: /* if( !is_numeric($model->assignedTo)) { // assigned to user
570: $profile = Profile::model()->findByAttributes(array('username'=>$model->assignedTo));
571: if(isset($profile)) // prevent error for actions assigned to 'Anyone'
572: $profile->updateGoogleCalendarEvent($model); // update action in Google Calendar if user has a Google Calendar
573: } else { // Assigned to group
574: $groups = Yii::app()->db->createCommand()->select('userId')->from('x2_group_to_user')->where("groupId={$model->assignedTo}")->queryAll();
575: foreach($groups as $group) {
576: $profile = Profile::model()->findByPk($group['userId']);
577: if(isset($profile)) // prevent error for actions assigned to 'Anyone'
578: $profile->updateGoogleCalendarEvent($model);
579: }
580: } */
581:
582: if($api == 0)
583: parent::update($model, $oldAttributes, $api);
584: else
585: return parent::update($model, $oldAttributes, $api);
586: }
587:
588: /**
589: * Updates a particular model.
590: * If update is successful, the browser will be redirected to the 'view' page.
591: * @param integer $id the ID of the model to be updated
592: */
593: public function actionUpdate($id){
594: $model = $this->loadModel($id);
595: $users = User::getNames();
596: $notifications = X2Model::model('Notification')->findAllByAttributes(array(
597: 'modelType' => 'Actions',
598: 'modelId' => $model->id,
599: 'type' => 'action_reminder'
600: ));
601: // Uncomment the following line if AJAX validation is needed
602: // $this->performAjaxValidation($model);
603:
604: if(isset($_POST['Actions'])){
605: $oldAttributes = $model->attributes;
606: $model->setX2Fields($_POST['Actions']);
607: if($model->lastUpdated != $oldAttributes['lastUpdated']){
608: $model->disableBehavior('X2TimestampBehavior');
609: }
610: if($model->dueDate != $oldAttributes['dueDate']){
611: $event = CActiveRecord::model('Events')
612: ->findByAttributes(
613: array(
614: 'type' => 'action_reminder',
615: 'associationType' => 'Actions',
616: 'associationId' => $model->id));
617: if(isset($event)){
618: $event->timestamp = $model->dueDate;
619: $event->update(array('timestamp'));
620: }
621: }
622:
623:
624:
625: // $this->update($model,$oldAttributes,'0');
626: if($model->save()){
627: if(Yii::app()->user->checkAccess('ActionsAdmin') ||
628: Yii::app()->settings->userActionBackdating){
629:
630: $events = X2Model::model('Events')->findAllByAttributes(array(
631: 'associationType' => 'Actions',
632: 'associationId' => $model->id,
633: ));
634: foreach($events as $event) {
635: $event->timestamp = $model->getRelevantTimestamp();
636: $event->update(array('timestamp'));
637: }
638: }
639: $model->syncGoogleCalendar('update');
640: // if the action has an association
641: if(isset($_GET['redirect']) && $model->associationType != 'none'){
642: if($model->associationType == 'product' ||
643: $model->associationType == 'products') {
644: $this->redirect(
645: array('/products/products/view', 'id' => $model->associationId));
646: //TODO: avoid such hackery
647: } elseif($model->associationType == 'Campaign') {
648: $this->redirect(
649: array('/marketing/marketing/view', 'id' => $model->associationId));
650: } else {
651: $this->redirect(
652: array(
653: '/'.$model->associationType.'/'.$model->associationType.'/view',
654: 'id' => $model->associationId)); // go back to the association
655: }
656: } elseif(!Yii::app()->request->isAjaxRequest){ // no association
657: $this->redirect(array('index')); // view the action
658: }else{
659: echo $this->renderPartial('_viewIndex', array('data' => $model), true);
660: return;
661: }
662: }
663: } else {
664:
665: /* Set assignedTo back into an array only before re-rendering the input box with
666: assignees selected */
667: $model->assignedTo = array_map(function($n){
668: return trim($n,',');
669: },explode(' ',$model->assignedTo));
670:
671: $this->render('update', array(
672: 'model' => $model,
673: 'users' => $users,
674: ));
675: }
676: }
677:
678: public function actionCopyEvent ($id) {
679: $modelClass = $this->modelClass;
680: $model = $this->loadModel ($id);
681: $model->setX2Fields ($_POST[$modelClass]);
682: $model->id = null;
683: $copy = new $modelClass;
684: $copy->setAttributes ($model->getAttributes (), false);
685: if ($copy->save ()) {
686: $copy->syncGoogleCalendar('create');
687: echo $this->ajaxResponse ('success');
688: } else {
689: echo $this->ajaxResponse ('failure');
690: }
691: }
692:
693: public function actionQuickUpdate($id){
694: $model = $this->loadModel($id);
695: if(isset($_POST['Actions'])){
696: $model->setX2Fields($_POST['Actions']);
697:
698: $model->dueDate = Formatter::parseDateTime($model->dueDate);
699: if($model->completeDate){
700: $model->completeDate = Formatter::parseDateTime($model->completeDate);
701: }elseif(empty($model->completeDate)){
702: $model->completeDate = $model->dueDate;
703: }
704: if($model->save()){
705: $model->syncGoogleCalendar('update');
706: }
707: if (isset($_POST['isEvent']) && $_POST['isEvent']) {
708: // Update calendar event
709: $event = X2Model::model('Events')->findByAttributes(array(
710: 'associationType' => 'Actions',
711: 'associationId' => $model->id,
712: ));
713: if ($event !== null) {
714: $event->timestamp = $model->dueDate;
715: $event->update(array('timestamp'));
716: }
717: }
718: }
719: }
720:
721: public function actionToggleSticky($id){
722: $action = X2Model::model('Actions')->findByPk($id);
723: if(isset($action)){
724: $action->sticky = !$action->sticky;
725: $action->update(array('sticky'));
726: echo $action->sticky;
727: }
728: }
729:
730: // Postpones due date (and sets action to incomplete)
731: /* public function actionTomorrow($id) {
732: $model = $this->loadModel($id);
733: $model->complete='No';
734: $model->dueDate=time()+86400; //set to tomorrow
735: if($model->save()){
736: if($model->associationType!='none')
737: $this->redirect(array($model->associationType.'/'.$model->associationId));
738: else
739: $this->redirect(array('view','id'=>$id));
740: }
741: } */
742:
743: /**
744: * API method to delete an action
745: * @param integer $id The id of the action
746: */
747: public function delete($id){
748: $model = $this->loadModel($id);
749: $this->cleanUpTags($model);
750: $model->delete();
751: }
752:
753: /**
754: * Deletes an action
755: * @param integer $id The id of the action
756: */
757: public function actionDelete($id){
758:
759: $model = $this->loadModel($id);
760: if(Yii::app()->request->isPostRequest){
761: // $this->cleanUpTags($model); // now in TagBehavior
762: $event = new Events;
763: $event->type = 'record_deleted';
764: $event->associationType = $this->modelClass;
765: $event->associationId = $model->id;
766: $event->text = $model->name;
767: $event->visibility = $model->visibility;
768: $event->user = Yii::app()->user->getName();
769: $event->save();
770: Events::model()->deleteAllByAttributes(array('associationType' => 'Actions', 'associationId' => $id, 'type' => 'action_reminder'));
771:
772: $model->syncGoogleCalendar('delete');
773:
774: /* if(!is_numeric($model->assignedTo)) { // assigned to user
775: $profile = Profile::model()->findByAttributes(array('username'=>$model->assignedTo));
776: if(isset($profile))
777: $profile->deleteGoogleCalendarEvent($model); // update action in Google Calendar if user has a Google Calendar
778: } else { // Assigned to group
779: $groups = Yii::app()->db->createCommand()->select('userId')->from('x2_group_to_user')->where("groupId={$model->assignedTo}")->queryAll();
780: foreach($groups as $group) {
781: $profile = Profile::model()->findByPk($group['userId']);
782: if(isset($profile))
783: $profile->deleteGoogleCalendarEvent($model);
784: } */
785:
786: $model->delete();
787: }else{
788: throw new CHttpException(400, 'Invalid request. Please do not repeat this request again.');
789: }
790: // if AJAX request (triggered by deletion via admin grid view), we should not redirect the browser
791: if(!isset($_GET['ajax']) && !Yii::app()->request->isAjaxRequest)
792: $this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('index'));
793: // Only report the success of a deleted record if this request wasn't made via mass actions
794: else if (!isset($_POST['gvSelection']))
795: echo 'success';
796: }
797:
798: /**
799: * Marks an action as complete and redirects back to the page it was completed on.
800: * @param integer $id The id of the action
801: */
802: public function actionComplete($id){
803: $model = $this->loadModel($id);
804: if(isset($_GET['notes'])){
805: $notes = $_GET['notes'];
806: }else{
807: $notes = null;
808: }
809:
810: if($model->isAssignedTo (Yii::app()->user->getName ()) ||
811: Yii::app()->params->isAdmin){ // make sure current user can edit this action
812:
813: if(isset($_POST['note']) && !empty($_POST['note']))
814: $model->actionDescription = $model->actionDescription."\n\n".$_POST['note'];
815:
816: // $model = $this->updateChangelog($model,'Completed');
817: $model->complete(null, $notes);
818:
819: // Actions::completeAction($id);
820: // $this->completeNotification('admin',$model->id);
821:
822: $createNew = isset($_GET['createNew']) || ((isset($_POST['submit']) && ($_POST['submit'] == 'completeNew')));
823: $redirect = isset($_GET['redirect']) || $createNew;
824:
825: if($redirect){
826: if($model->associationType != 'none' && !$createNew){ // if the action has an association
827: $this->redirect(array('/'.$model->associationType.'/'.$model->associationType.'/view', 'id' => $model->associationId)); // go back to the association
828: }else{ // no association
829: if($createNew)
830: $this->redirect(array('/actions/actions/create')); // go to blank 'create action' page
831: else
832: $this->redirect(array('index')); // view the action
833: }
834: } elseif(Yii::app()->request->isAjaxRequest){
835: echo "Success";
836: }else{
837: $this->redirect(array('index'));
838: }
839: }elseif(Yii::app()->request->isAjaxRequest){
840: echo "Failure";
841: }else{
842: $this->redirect(array('/actions/actions/invalid'));
843: }
844: }
845:
846: /**
847: * Marks an action as incomplete and clears the completedBy field.
848: * @param integer $id The id of the action
849: */
850: public function actionUncomplete($id){
851: $model = $this->loadModel($id);
852: if($model->uncomplete()){
853: if(Yii::app()->request->isAjaxRequest) {
854: echo 'success';
855: }else{
856: $this->redirect(array('/actions/'.$id));
857: }
858: }
859: }
860:
861: /**
862: * Called when a Contact opens an email sent from Inline Email Form. Inline Email Form
863: * appends an image to the email with src pointing to this function. This function
864: * creates an action associated with the Contact indicating that the email was opened.
865: *
866: * @param integer $uid The unique id of the recipient
867: * @param string $type 'open', 'click', or 'unsub'
868: *
869: */
870: public function actionEmailOpened($uid, $type){
871: // If the request is coming from within the web application, ignore it.
872: $referrer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
873: $baseUrl = Yii::app()->request->getBaseUrl(true);
874: $fromApp = strpos($referrer, $baseUrl) === 0;
875:
876: if($type == 'open' && !$fromApp){
877: $track = TrackEmail::model()->findByAttributes(array('uniqueId' => $uid));
878: $track->recordEmailOpen();
879: }
880: //return a one pixel transparent png
881: header('Content-Type: image/png');
882: echo base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAAXNSR0IArs4c6QAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAC0lEQVQI12NgYAAAAAMAASDVlMcAAAAASUVORK5CYII=');
883: }
884:
885: // Lists all actions assigned to this user
886: public function actionIndex(){
887: if(isset($_GET['toggleView']) && $_GET['toggleView']){
888: if(Yii::app()->params->profile->oldActions){
889: Yii::app()->params->profile->oldActions = 0;
890: }else{
891: Yii::app()->params->profile->oldActions = 1;
892: }
893: Yii::app()->params->profile->update(array('oldActions'));
894: $this->redirect(array('index'));
895: }
896:
897: $model = new Actions('search');
898: if(!isset(Yii::app()->params->profile->oldActions) ||
899: !Yii::app()->params->profile->oldActions){
900:
901: if(!empty($_POST) || !empty(Yii::app()->params->profile->actionFilters)){
902: if(isset($_POST['complete'], $_POST['assignedTo'], $_POST['dateType'],
903: $_POST['dateRange'], $_POST['orderType'], $_POST['order'], $_POST['start'],
904: $_POST['end'])){
905:
906: $complete = $_POST['complete'];
907: $assignedTo = $_POST['assignedTo'];
908: $dateType = $_POST['dateType'];
909: $dateRange = $_POST['dateRange'];
910: $orderType = $_POST['orderType'];
911: $order = $_POST['order'];
912: $start = $_POST['start'];
913: $end = $_POST['end'];
914: if($dateRange != 'range'){
915: $start = null;
916: $end = null;
917: }
918: $filters = array(
919: 'complete' => $complete, 'assignedTo' => $assignedTo,
920: 'dateType' => $dateType, 'dateRange' => $dateRange,
921: 'orderType' => $orderType, 'order' => $order, 'start' => $start,
922: 'end' => $end);
923: }elseif(!empty(Yii::app()->params->profile->actionFilters)){
924: $filters = json_decode(Yii::app()->params->profile->actionFilters, true);
925: }
926: $condition = Actions::createCondition($filters);
927: $dataProvider = $model->search($condition, Actions::ACTION_INDEX_PAGE_SIZE);
928: $params = $filters;
929: }else{
930: $dataProvider = $model->search(null, Actions::ACTION_INDEX_PAGE_SIZE);
931: $params = array();
932: }
933: $this->render('index', array(
934: 'model' => $model,
935: 'dataProvider' => $dataProvider,
936: 'params' => $params,
937: ));
938: }else{
939: $this->render('oldIndex', array('model' => $model));
940: }
941: }
942:
943: /**
944: * List all public actions
945: */
946: public function actionViewAll(){
947: $model = new Actions('search');
948: $profile = Profile::model()->findByPk(Yii::app()->user->id);
949:
950: $this->render(
951: 'oldIndex',
952: array(
953: 'model' => $model,
954: 'showActions' => $profile->showActions,
955: )
956: );
957: }
958:
959: public function actionViewGroup(){
960: $model = new Actions('search');
961: $this->render('oldIndex', array('model' => $model));
962: }
963:
964: // display error page
965: public function actionInvalid(){
966: $this->render('invalid');
967: }
968:
969: public function actionParseType(){
970: if(isset($_POST['Actions']['associationType'])){
971: $type = $_POST['Actions']['associationType'];
972: if($modelName = X2Model::getModelName($type)){
973: $linkModel = $modelName;
974: if(class_exists($linkModel)){
975: if($linkModel == "X2Calendar")
976: $linkSource = ''; // Return no data to disable autocomplete on actions/update
977: else
978: $linkSource = $this->createUrl(X2Model::model($linkModel)->autoCompleteSource);
979: }else{
980: $linkSource = "";
981: }
982: echo $linkSource;
983: }else{
984: echo '';
985: }
986: }else{
987: echo '';
988: }
989: }
990:
991: public function getAssociation($type, $id){
992: return X2Model::getAssociationModel($type, $id);
993: }
994:
995: /**
996: * Returns the data model based on the primary key given in the GET variable.
997: * If the data model is not found, an HTTP exception will be raised.
998: * @param integer the ID of the model to be loaded
999: */
1000: public function loadModel($id){
1001: $model = CActiveRecord::model('Actions')->findByPk((int) $id);
1002: //$dueDate=$model->dueDate;
1003: //$model=Actions::changeDates($model);
1004: // if($model->associationId!=0) {
1005: // $model->associationName = $this->parseName(array($model->associationType,$model->associationId));
1006: // } else
1007: // $model->associationName = 'None';
1008:
1009: if($model === null)
1010: throw new CHttpException(404, 'The requested page does not exist.');
1011: return $model;
1012: }
1013:
1014:
1015: public function actionGetItems($term){
1016: X2LinkableBehavior::getItems ($term, 'subject');
1017: }
1018:
1019: /**
1020: * Performs AJAX validation.
1021: * @param CModel the model to be validated
1022: */
1023: protected function performAjaxValidation($model){
1024: if(isset($_POST['ajax']) && $_POST['ajax'] === 'actions-form'){
1025: echo CActiveForm::validate($model);
1026: Yii::app()->end();
1027: }
1028: }
1029:
1030: }
1031: