1. namtrankhanh
  2. davidtranZurmoFork

Commits

Jason Green  committed 4143164

Work on comments and conversations

  • Participants
  • Parent commits a54723d
  • Branches convoandmissions

Comments (0)

Files changed (16)

File app/protected/extensions/zurmoinc/framework/widgets/MbMenu.php

View file
  • Ignore whitespace
                 }
                 if ((isset($item['ajaxLinkOptions'])))
                 {
-                    echo CHtml::ajaxLink('<span>' . $item['label'] . '</span>', $item['url'], $item['ajaxLinkOptions'], $htmlOptions);
+                    echo CHtml::ajaxLink('<span>' . $item['label'] . '</span>' .
+                         static::resolveAndGetSpanAndDynamicLabelContent($item), $item['url'], $item['ajaxLinkOptions'], $htmlOptions);
                 }
                 elseif (isset($item['url']))
                 {
-                    echo CHtml::link('<span></span><span>' . $item['label'] . '</span>', $item['url'], $htmlOptions);
+                    echo CHtml::link('<span></span><span>' . $item['label'] . '</span>' .
+                         static::resolveAndGetSpanAndDynamicLabelContent($item), $item['url'], $htmlOptions);
                 }
                 else
                 {
-                    echo CHtml::link('<span>' . $item['label'] . '</span>', "javascript:void(0);", $htmlOptions);
+                    echo CHtml::link('<span>' . $item['label'] . '</span>' .
+                         static::resolveAndGetSpanAndDynamicLabelContent($item), "javascript:void(0);", $htmlOptions);
                 }
                 if (isset($item['items']) && count($item['items']))
                 {
             }
         }
 
+        protected static function resolveAndGetSpanAndDynamicLabelContent($item)
+        {
+            if(isset($item['dynamicLabelContent']))
+            {
+                return CHtml::tag('span', array(), $item['dynamicLabelContent']);
+            }
+        }
+
         protected function normalizeItems($items, $route, &$active, $ischild = 0)
         {
             foreach ($items as $i => $item)

File app/protected/modules/comments/controllers/DefaultController.php

View file
  • Ignore whitespace
             $getParams                = array('relatedModelId'           => $relatedModelId,
                                               'relatedModelClassName'    => $relatedModelClassName,
                                               'relatedModelRelationName' => $relatedModelRelationName);
-            $view                     = new CommentsForRelatedModelView('default', 'comments', $commentsData,
+            $relatedModel             = $relatedModelClassName::getById((int)$relatedModelId);
+            $view                     = new CommentsForRelatedModelView('default', 'comments', $commentsData, $relatedModel,
                                                                         $pageSize, $getParams);
             $content                  = $view->render();
             Yii::app()->getClientScript()->setToAjaxMode();
             echo $content;
         }
 
+        public function actionDeleteViaAjax($id)
+        {
+            $getData                  = GetUtil::getData();
+            $relatedModelId           = ArrayUtil::getArrayValue($getData, 'relatedModelId');
+            $relatedModelClassName    = ArrayUtil::getArrayValue($getData, 'relatedModelClassName');
+            $comment                  = Comment::getById(intval($id));
+            $relatedModel             = $relatedModelClassName::getById(intval($relatedModelId));
+            if($comment->createdByUser->id != Yii::app()->user->userModel->id &&
+               $relatedModel->owner->id    != Yii::app()->user->userModel->id)
+            {
+                $messageView = new AccessFailureAjaxView();
+                $view        = new AjaxPageView($messageView);
+                echo $view->render();
+                Yii::app()->end(0, false);
+            }
+            $deleted = $comment->delete();
+            if(!$deleted)
+            {
+                throw new FailedToDeleteModelException();
+            }
+        }
+
         protected function actionInlineEditValidate($model)
         {
             $postData                      = PostUtil::getData();

File app/protected/modules/comments/views/CommentsForRelatedModelView.php

View file
  • Ignore whitespace
 
         protected $commentsData;
 
+        protected $relatedModel;
+
         protected $pageSize;
 
         protected $getParams;
 
-        public function __construct($controllerId, $moduleId, $commentsData, $pageSize, $getParams)
+        public function __construct($controllerId, $moduleId, $commentsData, Item $relatedModel, $pageSize, $getParams)
         {
             assert('is_string($controllerId)');
             assert('is_string($moduleId)');
             assert('is_array($commentsData)');
+            assert('$relatedModel->id > 0');
             assert('is_int($pageSize) || $pageSize == null');
             assert('is_array($getParams)');
             $this->controllerId           = $controllerId;
             $this->moduleId               = $moduleId;
             $this->commentsData           = $commentsData;
+            $this->relatedModel           = $relatedModel;
             $this->pageSize               = $pageSize;
             $this->getParams              = $getParams;
         }
                     $stringContent .= '<br/>';
                     $stringContent .= FileModelDisplayUtil::renderFileDataDetailsWithDownloadLinksContent($comment, 'files');
                 }
+                if($comment->createdByUser == Yii::app()->user->userModel ||
+                   $this->relatedModel->createdByUser == Yii::app()->user->userModel ||
+                   ($this->relatedModel instanceof OwnedSecurableItem && $this->relatedModel->owner == Yii::app()->user->userModel))
+                {
+                    $stringContent .= CHtml::tag('span', array(), $this->renderDeleteLinkContent($comment));
+                }
                 $content .= '<tr>';
                 $content .= '<td>' . $stringContent . '</td>';
                 $content .= '</tr>';
             return $content;
         }
 
+        protected function renderDeleteLinkContent(Comment $comment)
+        {
+            $url     =   Yii::app()->createUrl($this->moduleId . '/' . $this->controllerId . '/deleteViaAjax',
+                            array_merge($this->getParams, array('id' => $comment->id)));
+            return       ZurmoHtml::ajaxLink(Yii::t('Default', 'Delete'), $url,
+                         array('type'     => 'GET',
+                               'complete' => "function(XMLHttpRequest, textStatus){
+                                              $('#deleteCommentLink" . $comment->id . "').closest('tr').remove();}"),
+                         array('id'         => 'deleteCommentLink' . $comment->id,
+                                'class'     => 'deleteCommentLink' . $comment->id,
+                                'namespace' => 'delete'));
+        }
+
         public function isUniqueToAPage()
         {
             return true;

File app/protected/modules/conversations/ConversationsModule.php

View file
  • Ignore whitespace
                                 'right' => self::RIGHT_ACCESS_CONVERSATIONS
                             ),
                         ),
+                        'dynamicLabelContent' => 'eval:ConversationsUtil::getUnreadCountTabMenuContentForCurrentUser()'
                     ),
                 ),
                 'shortcutsCreateMenuItems' => array(

File app/protected/modules/conversations/controllers/DefaultController.php

View file
  • Ignore whitespace
             ControllerSecurityUtil::resolveAccessCanCurrentUserReadModel($conversation);
             AuditEvent::logAuditEvent('ZurmoModule', ZurmoModule::AUDIT_EVENT_ITEM_VIEWED,
                                       array(strval($conversation), 'ConversationsModule'), $conversation);
+            ConversationsUtil::markUserHasReadLatest($conversation, Yii::app()->user->userModel);
             $detailsAndRelationsView = $this->makeDetailsAndRelationsView($conversation, 'ConversationsModule',
                                                                           'ConversationDetailsAndRelationsView',
                                                                           Yii::app()->request->getRequestUri());
             $this->redirect(array($this->getId() . '/index'));
         }
 
-        public function actionDeleteCommentViaAjax($conversationId, $commentId)
-        {
-            $comment          = Comment::getById(intval($commentId));
-            $conversation     = Conversation::getById(intval($conversationId));
-            if($comment->createdByUser->id != Yii::app()->user->userModel->id &&
-               $conversation->owner->id    != Yii::app()->user->userModel->id)
-            {
-                $messageView = new AccessFailureAjaxView();
-                $view        = new AjaxPageView($messageView);
-                echo $view->render();
-                Yii::app()->end(0, false);
-            }
-            $deleted = $comment->delete();
-            if(!$deleted)
-            {
-                throw new FailedToDeleteModelException();
-            }
-        }
-
         /**
          * (non-PHPdoc)
          * @see ZurmoModuleController::actionCreateFromRelation()

File app/protected/modules/conversations/forms/ConversationItemForm.php

View file
  • Ignore whitespace
      ********************************************************************************/
 
     /**
-     * Helps manage related models to an Activity.
+     * Helps manage related models to a Conversation.
      */
     class ConversationItemForm extends RelatedItemForm
     {

File app/protected/modules/conversations/models/Conversation.php

View file
  • Ignore whitespace
             return 'ConversationGamification';
         }
 
+        /**
+         * Alter hasReadLatest and/or ownerHasReadLatest based on comments being added.
+         * (non-PHPdoc)
+         * @see Item::beforeSave()
+         */
         protected function beforeSave()
         {
             if (parent::beforeSave())
                 if($this->comments->isModified() || $this->getIsNewModel())
                 {
                     $this->unrestrictedSet('latestDateTime', DateTimeUtil::convertTimestampToDbFormatDateTime(time()));
+                    if($this->getIsNewModel())
+                    {
+                        $this->ownerHasReadLatest = true;
+                    }
                 }
                 if($this->comments->isModified())
                 {

File app/protected/modules/conversations/rules/ConversationMashableActivityRules.php

View file
  • Ignore whitespace
             }
         }
 
+        /**
+         * (non-PHPdoc)
+         * @see MashableActivityRules::getLatestActivityExtraDisplayStringByModel()
+         */
         public function getLatestActivityExtraDisplayStringByModel($model)
         {
             assert('$model instanceof Conversation');

File app/protected/modules/conversations/tests/unit/ConversationTest.php

View file
  • Ignore whitespace
             $this->assertEquals($fileModel,                       $conversation->files->offsetGet(0));
             $this->assertEquals(1,                                $conversation->conversationParticipants->count());
             $this->assertEquals($steven,                          $conversation->conversationParticipants->offsetGet(0)->person);
-            $this->assertEquals(0,                                $conversation->ownerHasReadLatest);
+            $this->assertEquals(1,                                $conversation->ownerHasReadLatest);
 
             //Clear conversation so permissions take properly.  Needed because we don't have changes between page loads.
             $id = $conversation->id;
             $this->assertEquals(2, $count);
         }
 
+        /**
+         * @depends testGetUnreadConversationCount
+         */
         public function testDeleteConversation()
         {
             $conversations = Conversation::getAll();

File app/protected/modules/conversations/tests/unit/ConversationsUtilTest.php

View file
  • Ignore whitespace
+<?php
+    /*********************************************************************************
+     * Zurmo is a customer relationship management program developed by
+     * Zurmo, Inc. Copyright (C) 2012 Zurmo Inc.
+     *
+     * Zurmo is free software; you can redistribute it and/or modify it under
+     * the terms of the GNU General Public License version 3 as published by the
+     * Free Software Foundation with the addition of the following permission added
+     * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
+     * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
+     * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
+     *
+     * Zurmo is distributed in the hope that it will be useful, but WITHOUT
+     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+     * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+     * details.
+     *
+     * You should have received a copy of the GNU General Public License along with
+     * this program; if not, see http://www.gnu.org/licenses or write to the Free
+     * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+     * 02110-1301 USA.
+     *
+     * You can contact Zurmo, Inc. with a mailing address at 113 McHenry Road Suite 207,
+     * Buffalo Grove, IL 60089, USA. or at email address contact@zurmo.com.
+     ********************************************************************************/
+
+    class ConversationsUtilTest extends ZurmoBaseTest
+    {
+        public static function setUpBeforeClass()
+        {
+            parent::setUpBeforeClass();
+            SecurityTestHelper::createSuperAdmin();
+            $super = User::getByUsername('super');
+            Yii::app()->user->userModel = $super;
+            AccountTestHelper::createAccountByNameForOwner('anAccount', $super);
+        }
+
+        public function setUp()
+        {
+            parent::setUp();
+            Yii::app()->user->userModel = User::getByUsername('super');
+        }
+
+        public function testMarkUserHasReadLatest()
+        {
+            $super                     = User::getByUsername('super');
+            $steven                    = UserTestHelper::createBasicUser('steven');
+
+            $conversation              = new Conversation();
+            $conversation->owner       = $super;
+            $conversation->subject     = 'My test subject';
+            $conversation->description = 'My test description';
+            $this->assertTrue($conversation->save());
+            $explicitReadWriteModelPermissions = ExplicitReadWriteModelPermissionsUtil::
+                                                 makeBySecurableItem($conversation);
+            $postData = array();
+            $postData['itemIds'] = $steven->getClassId('Item');
+            ConversationParticipantsUtil::resolveConversationHasManyParticipantsFromPost(
+                                            $conversation, $postData, $explicitReadWriteModelPermissions);
+            $this->assertTrue($conversation->save());
+            $success = ExplicitReadWriteModelPermissionsUtil::
+                        resolveExplicitReadWriteModelPermissions($conversation, $explicitReadWriteModelPermissions);
+            $this->assertTrue($success);
+
+            $id = $conversation->id;
+            $conversation->forget();
+            unset($conversation);
+
+            $conversation = Conversation::getById($id);
+            $this->assertEquals(1, $conversation->ownerHasReadLatest);
+            $this->assertEquals(0, $conversation->conversationParticipants->offsetGet(0)->hasReadLatest);
+
+            //After running for super, nothing will change.
+            ConversationsUtil::markUserHasReadLatest($conversation, $super);
+            $id = $conversation->id;
+            $conversation->forget();
+            unset($conversation);
+            $conversation = Conversation::getById($id);
+            $this->assertEquals(1, $conversation->ownerHasReadLatest);
+            $this->assertEquals(0, $conversation->conversationParticipants->offsetGet(0)->hasReadLatest);
+
+            //After running for steven, it will show read for him.
+            ConversationsUtil::markUserHasReadLatest($conversation, $steven);
+            $id = $conversation->id;
+            $conversation->forget();
+            unset($conversation);
+            $conversation = Conversation::getById($id);
+            $this->assertEquals(1, $conversation->ownerHasReadLatest);
+            $this->assertEquals(1, $conversation->conversationParticipants->offsetGet(0)->hasReadLatest);
+
+            $conversation->ownerHasReadLatest = false;
+            $this->assertTrue($conversation->save());
+            $id = $conversation->id;
+            $conversation->forget();
+            unset($conversation);
+            $conversation = Conversation::getById($id);
+            $this->assertEquals(0, $conversation->ownerHasReadLatest);
+            $this->assertEquals(1, $conversation->conversationParticipants->offsetGet(0)->hasReadLatest);
+
+
+            //Now try for Steven, nothing changes
+            ConversationsUtil::markUserHasReadLatest($conversation, $steven);
+            $id = $conversation->id;
+            $conversation->forget();
+            unset($conversation);
+            $conversation = Conversation::getById($id);
+            $this->assertEquals(0, $conversation->ownerHasReadLatest);
+            $this->assertEquals(1, $conversation->conversationParticipants->offsetGet(0)->hasReadLatest);
+
+            //Now try for super, should change
+            ConversationsUtil::markUserHasReadLatest($conversation, $super);
+            $id = $conversation->id;
+            $conversation->forget();
+            unset($conversation);
+            $conversation = Conversation::getById($id);
+            $this->assertEquals(1, $conversation->ownerHasReadLatest);
+            $this->assertEquals(1, $conversation->conversationParticipants->offsetGet(0)->hasReadLatest);
+        }
+    }
+?>

File app/protected/modules/conversations/tests/unit/walkthrough/ConversationsUserWalkthroughTest.php

View file
  • Ignore whitespace
      ********************************************************************************/
 
     /**
-     * Conversations Module Super User Walkthrough.
+     * Conversations Module User Walkthrough.
      * Walkthrough for the users of all possible controller actions.
-     * Since this is a super user, he should have access to all controller actions
-     * without any exceptions being thrown.
      */
     class ConversationsUserWalkthroughTest extends ZurmoWalkthroughBaseTest
     {
             $this->assertEquals($conversations[0]->comments->offsetGet(1)->createdByUser->id, $mary->id);
             $superCommentId = $conversations[0]->comments->offsetGet(0)->id;
             $this->assertEquals($conversations[0]->comments->offsetGet(0)->createdByUser->id, $super->id);
-            $this->setGetArray(array('commentId' => $maryCommentId, 'conversationId' => $conversations[0]->id));
-            $this->runControllerWithNoExceptionsAndGetContent('conversations/default/deleteCommentViaAjax', true);
+            $this->setGetArray(array('relatedModelId'             => $conversations[0]->id,
+                                     'relatedModelClassName'      => 'Conversation',
+                                     'relatedModelRelationName'   => 'comments',
+                                     'id'               		  => $maryCommentId));
+            $this->runControllerWithNoExceptionsAndGetContent('comments/default/deleteViaAjax', true);
             $conversationId  = $conversations[0]->id;
             $conversations[0]->forget();
             $conversation = Conversation::getById($conversationId);
             $this->assertEquals(1, $conversation->comments->count());
 
             //new test - mary cannot delete a comment she did not write.
-            $this->setGetArray(array('commentId' => $superCommentId, 'conversationId' => $conversations[0]->id));
-            $this->runControllerShouldResultInAjaxAccessFailureAndGetContent('conversations/default/deleteCommentViaAjax');
+            $this->setGetArray(array('relatedModelId'             => $conversations[0]->id,
+                                     'relatedModelClassName'      => 'Conversation',
+                                     'relatedModelRelationName'   => 'comments',
+                                     'id'               		  => $superCommentId));
+            $this->runControllerShouldResultInAjaxAccessFailureAndGetContent('comments/default/deleteViaAjax');
             $conversationId  = $conversations[0]->id;
             $conversations[0]->forget();
             $conversation = Conversation::getById($conversationId);
             $this->assertEquals(1, $conversation->comments->count());
 
             $super          = $this->logoutCurrentUserLoginNewUserAndGetByUsername('super');
-            //new test , super can edit the conversation
+            //new test , super can view and edit the conversation
             $this->setGetArray(array('id' => $conversation->id));
             $this->runControllerWithNoExceptionsAndGetContent('conversations/default/details');
+            $this->runControllerWithNoExceptionsAndGetContent('conversations/default/edit');
 
             //new test , super can delete the conversation
             $this->setGetArray(array('id' => $conversation->id));

File app/protected/modules/conversations/utils/ConversationParticipantsUtil.php

View file
  • Ignore whitespace
      ********************************************************************************/
 
     /**
-     * Helper class for conversation participant related logic.
+     * Helper class for conversation participant logic.
      */
     class ConversationParticipantsUtil
     {
+        /**
+         * Given a Conversation and User, determine if the user is already a conversationParticipant.
+         * @param Conversation $model
+         * @param User $user
+         */
         public static function isUserAParticipant(Conversation $model, User $user)
         {
             if ($model->conversationParticipants->count() > 0)

File app/protected/modules/conversations/utils/ConversationZurmoControllerUtil.php

View file
  • Ignore whitespace
      ********************************************************************************/
 
     /**
-     * Extended class to support conversation participants
+     * Class helps support adding/removing conversation participants while saving a conversation from a post.
      */
     class ConversationZurmoControllerUtil extends ModelHasFilesAndRelatedItemsZurmoControllerUtil
     {
         }
 
         /**
-         * Override to handle incoming file upload information.
+         * Override to handle incoming conversation participant information
          * (non-PHPdoc)
          * @see ModelHasRelatedItemsZurmoControllerUtil::afterSetAttributesDuringSave()
          */

File app/protected/modules/conversations/utils/ConversationsUtil.php

View file
  • Ignore whitespace
      */
     class ConversationsUtil
     {
+        /**
+         * Renders string content for the conversation subject and either the description or latest conversation comment
+         * if it exists.
+         * @param Conversation $conversation
+         * @return string
+         */
         public static function renderSubjectAndLatestForDisplayView(Conversation $conversation)
         {
             $url            = Yii::app()->createUrl('/conversations/default/details', array('id' => $conversation->id));
             return $content = ZurmoHtml::link($content, $url);
         }
 
+        /**
+         * Given a conversation, render the latest comment or the conversation description a comment does not exist.
+         * @param Conversation $conversation
+         * @return string
+         */
         public static function renderDescriptionOrLatestCommentContent(Conversation $conversation)
         {
             $content = null;
             }
             return $content;
         }
+
+        /**
+         * For the current user, render a string of how many unread conversations exist for the user.
+         * @return string
+         */
+        public static function getUnreadCountTabMenuContentForCurrentUser()
+        {
+            return Conversation::getUnreadCountByUser(Yii::app()->user->userModel);
+        }
+
+        /**
+         * Given a conversation and a user, mark that the user has either read the latest comment as a conversation
+         * participant, or if the user is the owner, than as the owner.
+         * @param Conversation $conversation
+         * @param User $user
+         */
+        public static function markUserHasReadLatest(Conversation $conversation, User $user)
+        {
+            assert('$conversation->id > 0');
+            assert('$user->id > 0');
+            $save = false;
+            if($user == $conversation->owner)
+            {
+                if(!$conversation->ownerHasReadLatest)
+                {
+                    $conversation->ownerHasReadLatest = true;
+                    $save                             = true;
+                }
+            }
+            else
+            {
+                foreach($conversation->conversationParticipants as $position => $participant)
+                {
+                    if($participant->person == $user && !$participant->hasReadLatest)
+                    {
+                        $conversation->conversationParticipants[$position]->hasReadLatest = true;
+                        $save                                                             = true;
+                    }
+                }
+            }
+            if($save)
+            {
+                $conversation->save();
+            }
+        }
     }
 ?>

File app/protected/modules/conversations/views/ConversationDetailsView.php

View file
  • Ignore whitespace
             return '<h1>' . strval($this->model) . "</h1>";
         }
 
-        /**
-         * Override to change the editableTemplate to place the label above the input.
-         * @see DetailsView::resolveElementDuringFormLayoutRender()
-         */
-        /**
-        protected function resolveElementDuringFormLayoutRender(& $element)
-        {
-            $element->nonEditableTemplate = '<td colspan="{colspan}">{content}</td>';
-        }
-        **/
-
         protected function renderAfterFormLayoutForDetailsContent()
         {
             $getParams    = array('relatedModelId'           => $this->model->id,
             $pageSize     = 5;
             $commentsData = Comment::getCommentsByRelatedModelTypeIdAndPageSize(get_class($this->model),
                                                                                 $this->modelId, ($pageSize + 1));
-            $view         = new CommentsForRelatedModelView('default', 'comments', $commentsData, $pageSize, $getParams);
+            $view         = new CommentsForRelatedModelView('default', 'comments', $commentsData, $this->model, $pageSize, $getParams);
             $content     .= $view->render();
             return $content;
         }

File app/protected/modules/zurmo/utils/MenuUtil.php

View file
  • Ignore whitespace
                 $items = MenuUtil::getVisibleAndOrderedTabMenuByCurrentUser();
                 GeneralCache::cacheEntry(self::getMenuViewItemsCacheIdentifier(), $items);
             }
+            static::resolveTabMenuForDynamicLabelContent($items);
             return $items;
         }
 
             }
             return $menuItems;
         }
+
+        protected static function resolveTabMenuForDynamicLabelContent(& $items)
+        {
+            foreach ($items as $key => $item)
+            {
+                if (isset($items[$key]['dynamicLabelContent']))
+                {
+                    MetadataUtil::resolveEvaluateSubString($items[$key]['dynamicLabelContent']);
+                    if(isset($items[$key]['items']))
+                    {
+                        static::resolveTabMenuForDynamicLabelContent($items[$key]['items']);
+                    }
+                }
+            }
+        }
     }
 ?>