Explorar el Código

Ticket #5186 - Tasks: Functionality improvements. Point 2.

AntonLV hace 1 año
padre
commit
d5bfd9f404

+ 58 - 21
modules/boonex/russian/data/langs/bx_tasks/ru.xml

@@ -13,7 +13,8 @@
     <string name="_bx_tasks_option_rss_num"><![CDATA[Количество элементов в ленте RSS]]></string>
     <string name="_bx_tasks_option_searchable_fields"><![CDATA[Поля для поиска]]></string>
     <string name="_bx_tasks_option_auto_activation_for_categories"><![CDATA[Автоактивация для категорий]]></string>
-  
+
+    <!-- Forms -->
     <string name="_bx_tasks_form_entry"><![CDATA[Задания]]></string>
     <string name="_bx_tasks_form_entry_display_add"><![CDATA[Добавить задание]]></string>
     <string name="_bx_tasks_form_entry_display_edit"><![CDATA[Редактировать задание]]></string>
@@ -76,13 +77,14 @@
     <string name="_bx_tasks_form_entry_input_sys_attachments"><![CDATA[Вложения]]></string>
     <string name="_bx_tasks_form_entry_input_sys_allow_comments"><![CDATA[Разрешить комментарии]]></string>
     <string name="_bx_tasks_form_entry_input_allow_comments"><![CDATA[Разрешить комментарии]]></string>
-	
     <string name="_bx_tasks_form_entry_input_sys_tasks_list"><![CDATA[Список заданий]]></string>
     <string name="_bx_tasks_form_entry_input_tasks_list"><![CDATA[Список заданий]]></string>
-    <string name="_bx_tasks_form_profile_input_sys_initial_members"><![CDATA[Назначить на]]></string>
-    <string name="_bx_tasks_form_profile_input_initial_members"><![CDATA[Назначить на]]></string>
+    <string name="_bx_tasks_form_entry_input_sys_initial_members"><![CDATA[Назначить на]]></string>
+    <string name="_bx_tasks_form_entry_input_initial_members"><![CDATA[Назначить на]]></string>
     <string name="_bx_tasks_form_entry_input_sys_date_due_date"><![CDATA[Срок]]></string>
     <string name="_bx_tasks_form_entry_input_date_due_date"><![CDATA[Срок]]></string>
+    <string name="_bx_tasks_form_entry_input_sys_estimate"><![CDATA[Временные затраты]]></string>
+    <string name="_bx_tasks_form_entry_input_estimate"><![CDATA[Временные затраты (в часах)]]></string>
 
     <string name="_bx_tasks_form_list_entry"><![CDATA[Списки заданий]]></string>
     <string name="_bx_tasks_form_list_entry_display_add"><![CDATA[Добавить Список заданий]]></string>
@@ -93,6 +95,21 @@
     <string name="_bx_tasks_form_list_entry_input_title"><![CDATA[Заголовок]]></string>
     <string name="_bx_tasks_form_list_entry_input_title_err"><![CDATA[Заголовок обязателен. Пожалуйста, заполните это поле]]></string>
 
+    <string name="_bx_tasks_form_time"><![CDATA[Время]]></string>
+    <string name="_bx_tasks_form_display_time_add"><![CDATA[Добавить время]]></string>
+    <string name="_bx_tasks_form_display_time_edit"><![CDATA[Редактировать время]]></string>
+    <string name="_bx_tasks_form_time_input_sys_sys"><![CDATA[Система]]></string>
+    <string name="_bx_tasks_form_time_input_sys_object_id"><![CDATA[ID объекта]]></string>
+    <string name="_bx_tasks_form_time_input_object_id"><![CDATA[Задание]]></string>
+    <string name="_bx_tasks_form_time_input_object_id_err"><![CDATA[Это поле обязательно. Пожалуйста, заполните его.]]></string>
+    <string name="_bx_tasks_form_time_input_sys_action"><![CDATA[Действие]]></string>
+    <string name="_bx_tasks_form_time_input_sys_submit"><![CDATA[Выполнить]]></string>
+    <string name="_bx_tasks_form_time_input_submit"><![CDATA[Выполнить]]></string>
+    <string name="_bx_tasks_form_time_input_sys_value"><![CDATA[Время]]></string>
+    <string name="_bx_tasks_form_time_input_value"><![CDATA[Время]]></string>
+    <string name="_bx_tasks_popup_title_time_add"><![CDATA[Добавить время]]></string>
+    <string name="_bx_tasks_popup_title_time_edit"><![CDATA[Редактировать время]]></string>
+
     <string name="_bx_tasks_category_form_entry"><![CDATA[Форма Категории]]></string>
     <string name="_bx_tasks_category_display_add"><![CDATA[Добавить категорию]]></string>
     <string name="_bx_tasks_category_display_edit"><![CDATA[Редактировать категорию]]></string>
@@ -101,6 +118,7 @@
     <string name="_bx_tasks_category_form_entry_input_value_err"><![CDATA[Значение обязательно. Пожалуйста, заполните это поле]]></string>
     <string name="_bx_tasks_lmi_cpt_categories"><![CDATA[Категории]]></string>
 
+    <!-- ACL -->
     <string name="_bx_tasks_acl_action_create_entry"><![CDATA[Создать Задание]]></string>
     <string name="_bx_tasks_acl_action_delete_entry"><![CDATA[Удалить Задание]]></string>
     <string name="_bx_tasks_acl_action_view_entry"><![CDATA[Просмотреть Задание]]></string>
@@ -108,6 +126,7 @@
     <string name="_bx_tasks_acl_action_edit_any_entry"><![CDATA[Редактировать Любое Задание]]></string>
     <string name="_bx_tasks_acl_action_delete_any_entry"><![CDATA[Удалять Любое Задание]]></string>
 
+    <!-- Pages -->
     <string name="_bx_tasks_page_title_sys_create_entry"><![CDATA[Новое Задание]]></string>
     <string name="_bx_tasks_page_title_create_entry"><![CDATA[Новое Задание]]></string>
     <string name="_bx_tasks_page_block_title_create_entry"><![CDATA[Новое Задание]]></string>
@@ -199,7 +218,18 @@
     <string name="_bx_tasks_page_block_title_calendar_in_context"><![CDATA[Календарь]]></string>
     <string name="_bx_tasks_page_block_title_sys_calendar_of_author"><![CDATA[Календарь]]></string>
     <string name="_bx_tasks_page_block_title_calendar_of_author"><![CDATA[Календарь]]></string>
+    <string name="_bx_tasks_page_title_sys_entries_time_in_context"><![CDATA[Мое время в контексте]]></string>
+    <string name="_bx_tasks_page_title_entries_time_in_context"><![CDATA[Мое время в {display_name}]]></string>
+    <string name="_bx_tasks_page_title_sys_entries_time_in_context_administration"><![CDATA[Всё время в контексте]]></string>
+    <string name="_bx_tasks_page_title_entries_time_in_context_administration"><![CDATA[Всё время в {display_name}]]></string>
+    <string name="_bx_tasks_page_block_title_sys_menu_in_context"><![CDATA[Меню просмотра контекста]]></string>
+    <string name="_bx_tasks_page_block_title_menu_in_context"><![CDATA[Меню]]></string>
+    <string name="_bx_tasks_page_block_title_sys_entries_time_in_context"><![CDATA[Мое время в контексте]]></string>
+    <string name="_bx_tasks_page_block_title_entries_time_in_context"><![CDATA[Мое время в <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_tasks_page_block_title_sys_entries_time_in_context_administration"><![CDATA[Всё время в контексте]]></string>
+    <string name="_bx_tasks_page_block_title_entries_time_in_context_administration"><![CDATA[Всё время в <a href="{profile_link}">{display_name}</a>]]></string>
 
+    <!-- Menus -->
     <string name="_bx_tasks_menu_title_entry_attachments"><![CDATA[Форма - Прикрепления]]></string>
     <string name="_bx_tasks_menu_set_title_entry_attachments"><![CDATA[Форма - Прикрепления]]></string>
     <string name="_bx_tasks_menu_item_title_system_cpa_photo_simple"><![CDATA[Добавить фото (один)]]></string>
@@ -214,7 +244,6 @@
     <string name="_bx_tasks_menu_item_title_cpa_file_simple"><![CDATA[Добавить файл]]></string>
     <string name="_bx_tasks_menu_item_title_system_cpa_file_html5"><![CDATA[Добавить файл (много)]]></string>
     <string name="_bx_tasks_menu_item_title_cpa_file_html5"><![CDATA[Добавить Файлы]]></string>
-
     <string name="_bx_tasks_menu_set_title_view_entry"><![CDATA[Задание - Действия]]></string>
     <string name="_bx_tasks_menu_title_view_entry"><![CDATA[Задание - Действия]]></string>
     <string name="_bx_tasks_menu_title_submenu"><![CDATA[Задания - Подменю]]></string>
@@ -261,27 +290,32 @@
     <string name="_bx_tasks_menu_item_title_admt_tasks"><![CDATA[Задания]]></string>
     <string name="_bx_tasks_menu_item_title_manage_my"><![CDATA[Мои Задания]]></string>
     <string name="_bx_tasks_menu_item_title_manage_all"><![CDATA[Все Задания]]></string>
-
+    <string name="_bx_tasks_menu_item_title_system_report_time"><![CDATA[Внести время]]></string>
     <string name="_bx_tasks_menu_item_title_system_set_completed"><![CDATA[Завершить]]></string>
     <string name="_bx_tasks_menu_item_title_set_completed"><![CDATA[Завершить]]></string>
     <string name="_bx_tasks_menu_item_title_system_set_uncompleted"><![CDATA[Открыть заново]]></string>
     <string name="_bx_tasks_menu_item_title_set_uncompleted"><![CDATA[Открыть заново]]></string>
+    <string name="_bx_tasks_menu_title_view_context_submenu"><![CDATA[Просмотр контекста - Подменю]]></string>
+    <string name="_bx_tasks_menu_set_title_view_context_submenu"><![CDATA[Просмотр контекста - Подменю]]></string>
+    <string name="_bx_tasks_menu_item_title_system_view_context_entries"><![CDATA[Задания]]></string>
+    <string name="_bx_tasks_menu_item_title_view_context_entries"><![CDATA[Задания]]></string>
+    <string name="_bx_tasks_menu_item_title_system_view_context_time"><![CDATA[Моё время]]></string>
+    <string name="_bx_tasks_menu_item_title_view_context_time"><![CDATA[Моё время]]></string>
+    <string name="_bx_tasks_menu_item_title_system_view_context_time_administration"><![CDATA[Всё время]]></string>
+    <string name="_bx_tasks_menu_item_title_view_context_time_administration"><![CDATA[Всё время]]></string>
 
-    <string name="_bx_tasks_grid_filter_item_title_adm_select_one_filter1"><![CDATA[Все статусы]]></string>
-    <string name="_bx_tasks_grid_filter_item_title_adm_select_one_filter2"><![CDATA[Любая аудитория]]></string>
-    <string name="_bx_tasks_grid_filter_item_title_adm_active"><![CDATA[Активные]]></string>
-    <string name="_bx_tasks_grid_filter_item_title_adm_hidden"><![CDATA[Спрятанные]]></string>
-    <string name="_bx_tasks_grid_column_title_adm_active"><![CDATA[Активные]]></string>
-    <string name="_bx_tasks_grid_column_title_adm_title"><![CDATA[Заголовок]]></string>
-    <string name="_bx_tasks_grid_action_title_adm_audit_content"><![CDATA[Аудит действий]]></string>
-    <string name="_bx_tasks_grid_column_title_adm_added"><![CDATA[Добавленные]]></string>
-    <string name="_bx_tasks_grid_column_title_adm_author"><![CDATA[Автор]]></string>>
-    <string name="_bx_tasks_grid_action_title_adm_add"><![CDATA[Добавить]]></string>
-    <string name="_bx_tasks_grid_action_title_adm_edit"><![CDATA[Редактировать]]></string>
-    <string name="_bx_tasks_grid_action_title_adm_delete"><![CDATA[Удалить]]></string>
-    <string name="_bx_tasks_grid_action_title_adm_more_actions"><![CDATA[Дополнительные Действия]]></string>
-    <string name="_bx_tasks_grid_action_err_delete"><![CDATA[Невозможно удалить выбранную(ные) заданиие(ния).]]></string>
-    <string name="_bx_tasks_grid_txt_account_manager"><![CDATA[Перенаправить в Управление аккаунтом.]]></string>
+    <!-- Grids -->
+    <string name="_bx_tasks_grid_column_title_tm_author_id"><![CDATA[Профиль]]></string>
+    <string name="_bx_tasks_grid_column_title_tm_object_id"><![CDATA[Задание]]></string>
+    <string name="_bx_tasks_grid_column_title_tm_text"><![CDATA[Примечание]]></string>
+    <string name="_bx_tasks_grid_column_title_tm_value"><![CDATA[Время]]></string>
+    <string name="_bx_tasks_grid_column_title_tm_date"><![CDATA[Добавлено]]></string>
+    <string name="_bx_tasks_grid_action_title_tm_add"><![CDATA[Добавить]]></string>
+    <string name="_bx_tasks_grid_action_title_tm_edit"><![CDATA[Редактировать]]></string>
+    <string name="_bx_tasks_grid_action_title_tm_delete"><![CDATA[Удалить]]></string>
+    <string name="_bx_tasks_grid_action_title_tm_calculate"><![CDATA[Рассчитать]]></string>
+    <string name="_bx_tasks_grid_filter_item_title_tm_select_one_author_id"><![CDATA[Любой исполнитель]]></string>
+    <string name="_bx_tasks_grid_filter_item_title_tm_select_one_object_id"><![CDATA[Любая задача]]></string>
 
     <string name="_bx_tasks_chart_growth"><![CDATA[Tasks: Growth]]></string>
     <string name="_bx_tasks_chart_growth_speed"><![CDATA[Tasks: Growth Speed]]></string>
@@ -341,6 +375,9 @@
     <string name="_bx_tasks_cat_Weddings"><![CDATA[Свадьба]]></string>
     <string name="_bx_tasks_cat_Womens_Fashion"><![CDATA[Женская мода]]></string>
 
+    <string name="_bx_tasks_report_time_do_report"><![CDATA[Время]]></string>
+    <string name="_bx_tasks_report_time_do_unreport"><![CDATA[Сбросить время]]></string>
+
     <string name="_bx_tasks_txt_all_entries_in"><![CDATA[<a href="{0}">Все Задания в {1} ({2})</a>]]></string>
     <string name="_bx_tasks_txt_all_entries_by"><![CDATA[{0} заданий]]></string>
     <string name="_bx_tasks_txt_sample_single"><![CDATA[задание]]></string>

+ 40 - 16
modules/boonex/tasks/classes/BxTasksConfig.php

@@ -31,6 +31,8 @@ class BxTasksConfig extends BxBaseModTextConfig
             // database tables
             'TABLE_ENTRIES' => $aModule['db_prefix'] . 'tasks',
             'TABLE_LISTS' => $aModule['db_prefix'] . 'lists',
+            'TABLE_TIME' => $aModule['db_prefix'] . 'time',
+            'TABLE_TIME_TRACK' => $aModule['db_prefix'] . 'time_track',
             'TABLE_POLLS' => '',
             'TABLE_ENTRIES_FULLTEXT' => 'title_text',
 
@@ -117,6 +119,7 @@ class BxTasksConfig extends BxBaseModTextConfig
             ),
             'OBJECT_VIDEO_TRANSCODER_HEIGHT' => '480px',
             'OBJECT_REPORTS' => 'bx_tasks',
+            'OBJECT_REPORTS_TIME' => 'bx_tasks_time',
             'OBJECT_VIEWS' => 'bx_tasks',
             'OBJECT_VOTES' => 'bx_tasks',
             'OBJECT_REACTIONS' => 'bx_tasks_reactions',
@@ -137,6 +140,9 @@ class BxTasksConfig extends BxBaseModTextConfig
             'OBJECT_FORM_LIST_ENTRY' => 'bx_tasks_list',
             'OBJECT_FORM_LIST_ENTRY_DISPLAY_ADD' => 'bx_tasks_list_entry_add',
             'OBJECT_FORM_LIST_ENTRY_DISPLAY_EDIT' => 'bx_tasks_list_entry_edit',
+            'OBJECT_FORM_TIME' => 'bx_tasks_time',
+            'OBJECT_FORM_TIME_DISPLAY_ADD' => 'bx_tasks_time_add',
+            'OBJECT_FORM_TIME_DISPLAY_EDIT' => 'bx_tasks_time_edit',
             'OBJECT_MENU_ENTRY_ATTACHMENTS' => 'bx_tasks_entry_attachments', // attachments menu in create/edit forms
             'OBJECT_MENU_ACTIONS_VIEW_ENTRY' => 'bx_tasks_view', // actions menu on view entry page
             'OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL' => 'bx_tasks_view_actions', // all actions menu on view entry page
@@ -145,8 +151,9 @@ class BxTasksConfig extends BxBaseModTextConfig
             'OBJECT_MENU_SUBMENU_VIEW_ENTRY_MAIN_SELECTION' => 'tasks-home', // first item in view entry submenu from main module submenu
             'OBJECT_MENU_SNIPPET_META' => 'bx_tasks_snippet_meta', // menu for snippet meta info
             'OBJECT_MENU_MANAGE_TOOLS' => 'bx_tasks_menu_manage_tools', //manage menu in content administration tools
-            'OBJECT_GRID_ADMINISTRATION' => 'bx_tasks_administration',
-            'OBJECT_GRID_COMMON' => 'bx_tasks_common',
+            'OBJECT_MENU_SUBMENU_VIEW_CONTEXT' => 'bx_tasks_view_context_submenu',
+            'OBJECT_GRID_TIME_ADMINISTRATION' => 'bx_tasks_time_administration',
+            'OBJECT_GRID_TIME' => 'bx_tasks_time',
             'OBJECT_GRID_CATEGORIES' => 'bx_tasks_categories',
             'OBJECT_UPLOADERS' => array('bx_tasks_simple', 'bx_tasks_html5'),
             'OBJECT_CONTENT_INFO' => 'bx_tasks',
@@ -200,12 +207,8 @@ class BxTasksConfig extends BxBaseModTextConfig
                 'txt_sample_score_up_single' => '_bx_tasks_txt_sample_score_up_single',
                 'txt_sample_score_down_single' => '_bx_tasks_txt_sample_score_down_single',
                 'form_field_author' => '_bx_tasks_form_entry_input_author',
-            	'grid_action_err_delete' => '_bx_tasks_grid_action_err_delete',
-            	'grid_txt_account_manager' => '_bx_tasks_grid_txt_account_manager',
-                'filter_item_active' => '_bx_tasks_grid_filter_item_title_adm_active',
-            	'filter_item_hidden' => '_bx_tasks_grid_filter_item_title_adm_hidden',
-            	'filter_item_select_one_filter1' => '_bx_tasks_grid_filter_item_title_adm_select_one_filter1',
-                'filter_item_select_one_filter2' => '_bx_tasks_grid_filter_item_title_adm_select_one_filter2',
+            	'filter_item_select_one_filter1' => '_bx_tasks_grid_filter_item_title_tm_select_one_author_id',
+                'filter_item_select_one_filter2' => '_bx_tasks_grid_filter_item_title_tm_select_one_object_id',
             	'menu_item_manage_my' => '_bx_tasks_menu_item_title_manage_my',
             	'menu_item_manage_all' => '_bx_tasks_menu_item_title_manage_all',
                 'txt_all_entries_by' => '_bx_tasks_txt_all_entries_by',
@@ -216,22 +219,28 @@ class BxTasksConfig extends BxBaseModTextConfig
             ),
         ));
         
-        $this->_aJsClasses = array_merge($this->_aJsClasses, array(
+        $this->_aJsClasses = array_merge($this->_aJsClasses, [
             'manage_tools' => 'BxTasksManageTools',
             'categories' => 'BxDolCategories',
             'tasks' => 'BxTasksView'
-        ));
+        ]);
 
-        $this->_aJsObjects = array_merge($this->_aJsObjects, array(
+        $this->_aJsObjects = array_merge($this->_aJsObjects, [
             'manage_tools' => 'oBxTasksManageTools',
             'categories' => 'oBxDolCategories',
             'tasks' => 'oBxTasksView'
-        ));
+        ]);
 
-        $this->_aGridObjects = array(
-            'common' => $this->CNF['OBJECT_GRID_COMMON'],
-            'administration' => $this->CNF['OBJECT_GRID_ADMINISTRATION'],
-        );
+        $this->_aGridObjects = [
+            'time' => $this->CNF['OBJECT_GRID_TIME'],
+            'time-administration' => $this->CNF['OBJECT_GRID_TIME_ADMINISTRATION'],
+        ];
+
+        $sPrefix = str_replace('_', '-', $this->_sName);
+        $this->_aHtmlIds = array_merge($this->_aHtmlIds, [
+            'time_popup' =>  $sPrefix . '-time-popup',
+            'total_popup' =>  $sPrefix . '-total-popup',
+        ]);
 
         $this->_bAttachmentsInTimeline = true;
     }
@@ -240,6 +249,21 @@ class BxTasksConfig extends BxBaseModTextConfig
     {
         return in_array($iState, [BX_TASKS_STATE_CANCELLED, BX_TASKS_STATE_DUPLICATE, BX_TASKS_STATE_DONE]);
     }
+    
+    public function timeS2I($s)
+    {
+        if(strpos($s, ':') === false)
+            return 0;
+
+        list($iH, $iM) = explode(':', $s);
+        return 60 * $iH + $iM;
+    }
+
+    public function timeI2S($i)
+    {
+        $iH = intdiv($i, 60);
+        return sprintf("%02d", $iH) . ':' . sprintf("%02d", $i - 60 * $iH);
+    }
 }
 
 /** @} */

+ 57 - 12
modules/boonex/tasks/classes/BxTasksDb.php

@@ -18,7 +18,7 @@ class BxTasksDb extends BxBaseModTextDb
         parent::__construct($oConfig);
     }
 	
-	public function getLists ($iContextId = 0)
+    public function getLists ($iContextId = 0)
     {
         $CNF = &$this->_oConfig->CNF;
 
@@ -26,7 +26,7 @@ class BxTasksDb extends BxBaseModTextDb
         return $this->getAll($sQuery);
     }
 	
-	public function getList ($iId = 0)
+    public function getList ($iId = 0)
     {
         $CNF = &$this->_oConfig->CNF;
 
@@ -45,22 +45,36 @@ class BxTasksDb extends BxBaseModTextDb
         $this->query($sQuery);
     }
 	
-    public function getTasks ($iContextId = 0, $iListId = 0)
+    public function getTasks ($iContextId = 0, $iListId = 0, $bWithStats = false)
     {
         $CNF = &$this->_oConfig->CNF;
 
-        $sWhereClause = " AND `" . $CNF['FIELD_ALLOW_VIEW_TO'] . "` = :" . $CNF['FIELD_ALLOW_VIEW_TO'] . " AND `" . $CNF['FIELD_TASKLIST'] . "` = :" . $CNF['FIELD_TASKLIST'];
+        $aBindings = [];
+        $sSelectClause = "`te`.*";
+        $sJoinClause = $sWhereClause = "";
 
-        $oCf = BxDolContentFilter::getInstance();
-        if($oCf->isEnabled())
-            $sWhereClause .= $oCf->getSQLParts($CNF['TABLE_ENTRIES'], $CNF['FIELD_CF']);
+        if(($sField = $CNF['FIELD_ALLOW_VIEW_TO']) && $iContextId) {
+            $aBindings[$sField] = $iContextId;
+            $sWhereClause .= " AND `te`.`" . $sField . "` = :" . $sField;
+        }
+
+        if(($sField = $CNF['FIELD_TASKLIST']) && $iListId) {
+            $aBindings[$sField] = $iListId;
+            $sWhereClause .= " AND `te`.`" . $sField . "` = :" . $sField;
+        }
+
+        if(($oCf = BxDolContentFilter::getInstance()) && $oCf->isEnabled())
+            $sWhereClause .= $oCf->getSQLParts('te', $CNF['FIELD_CF']);
+
+        if($bWithStats) {
+            $sSelectClause .= $this->prepareAsString(", (SELECT SUM(`value`) FROM `" . $CNF['TABLE_TIME_TRACK'] . "` WHERE `object_id`=`te`.`id` AND `author_id`=?) AS `time`, `tt`.`sum` AS `time_total`", bx_get_logged_profile_id());
 
-        return $this->getAll("SELECT * FROM `" . $CNF['TABLE_ENTRIES'] . "` WHERE 1" . $sWhereClause, [
-            $CNF['FIELD_ALLOW_VIEW_TO'] => $iContextId,
-            $CNF['FIELD_TASKLIST'] => $iListId
-        ]);
+            $sJoinClause .= " LEFT JOIN `" . $CNF['TABLE_TIME'] . "` AS `tt` ON `te`.`id`=`tt`.`object_id`";
+        }
+
+        return $this->getAll("SELECT " . $sSelectClause . " FROM `" . $CNF['TABLE_ENTRIES'] . "` AS `te`" . $sJoinClause . " WHERE 1" . $sWhereClause, $aBindings);
     }
-	
+
     public function getEntriesByDate($sDateFrom, $sDateTo, $aSQLPart = array())
     {
         // validate input data
@@ -140,6 +154,37 @@ class BxTasksDb extends BxBaseModTextDb
 
         return count($aResult) == (int)$this->query("UPDATE `" . $CNF['TABLE_ENTRIES'] . "` SET `" . $CNF['FIELD_EXPIRED'] . "` = '1' WHERE `id` IN (" . $this->implode_escape($aResult) . ")") ? $aResult : false;
     }
+    
+    public function getTimeTracks($aParams = []) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aMethod = ['name' => 'getAll', 'params' => [0 => 'query']];
+        $sSelectClause = '`ttt`.*';
+        $sJoinClause = $sWhereClause = $sOrderClause = '';
+
+        if(!empty($aParams))
+            switch($aParams['sample']) {
+                case 'id':
+                    $aMethod['name'] = 'getRow';
+                    $aMethod['params'][1] = [
+                        'id' => $aParams['id']
+                    ];
+
+                    $sWhereClause = "AND `ttt`.`id` = :id";
+                    break;
+            }
+
+        if(!empty($sOrderClause))
+            $sOrderClause = "ORDER BY " . $sOrderClause;
+
+        $aMethod['params'][0] = "SELECT 
+                " . $sSelectClause . " 
+            FROM `" . $CNF['TABLE_TIME_TRACK'] . "` AS `ttt` " . $sJoinClause . " 
+            WHERE 1 " . $sWhereClause . " " . $sOrderClause;
+
+        return call_user_func_array([$this, $aMethod['name']], $aMethod['params']);
+    }
 }
 
 /** @} */

+ 197 - 0
modules/boonex/tasks/classes/BxTasksGridTime.php

@@ -0,0 +1,197 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT 
+ * @defgroup    Tasks Tasks
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+require_once('BxTasksGridTimeAdministration.php');
+
+class BxTasksGridTime extends BxTasksGridTimeAdministration
+{
+    protected $_iLogged;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_iLogged = bx_get_logged_profile_id();
+    }
+
+    public function performActionAdd()
+    {
+        $sAction = 'add';
+
+        $oForm = $this->_getFormObject($sAction);
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            $aValsToAdd = [
+                'author_id' => $this->_iLogged,
+                'author_nip' => bx_get_ip_hash(getVisitorIP()),
+                'value' => $this->_oModule->_oConfig->timeS2I($oForm->getCleanValue('value')),
+                'date' => time()
+            ];
+
+            $iTrackId = $oForm->insert($aValsToAdd);
+            if(!$iTrackId)
+                return $this->_getActionResult(['msg' => _t('_bx_tasks_txt_err_cannot_perform_action')]);
+
+            $sSystem = $oForm->getCleanValue('sys');
+            $iObjectId = $oForm->getCleanValue('object_id');
+            if(($oTime = BxDolReport::getObjectInstance($sSystem, $iObjectId)) && $oTime->isEnabled())
+                $oTime->putReport($iObjectId, $this->_iLogged, $iTrackId);
+
+            return $this->_bIsApi ? [] : echoJson(['grid' => $this->getCode(false), 'blink' => $iTrackId]);    
+        }
+
+        if($this->_bIsApi)
+            return $this->getFormBlockAPI($oForm, $sAction);
+
+        $sContent = BxTemplFunctions::getInstance()->popupBox($this->_oModule->_oConfig->getHtmlIds('time_popup'), _t('_bx_tasks_popup_title_time_add'), $this->_oModule->_oTemplate->parseHtmlByName('popup_time.html', [
+            'form_id' => $oForm->getId(),
+            'form' => $oForm->getCode(true),
+            'object' => $this->_sObject,
+            'action' => $sAction
+        ]));
+
+        return echoJson(['popup' => ['html' => $sContent, 'options' => ['closeOnOuterClick' => false]]]);
+    }
+
+    public function performActionEdit()
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sAction = 'edit';
+
+        $aIds = $this->_getIds();
+        if($aIds === false)
+            return $this->_getActionResult([]);
+
+        $iTrack = array_shift($aIds);
+        $aTrack = $this->_oModule->_oDb->getTimeTracks(['sample' => 'id', 'id' => $iTrack]);
+        if(empty($aTrack) || !is_array($aTrack))
+            return $this->_getActionResult([]);
+
+        $oForm = $this->_getFormObject($sAction, $iTrack);
+        $oForm->initChecker(array_merge($aTrack, ['value' => $this->_oModule->_oConfig->timeI2S($aTrack['value'])]));
+        if($oForm->isSubmittedAndValid()) {
+            $aValsToAdd = [
+                'value' => $this->_oModule->_oConfig->timeS2I($oForm->getCleanValue('value')),
+            ];
+
+            if(!$oForm->update($iTrack, $aValsToAdd))
+                return $this->_getActionResult(['msg' => _t('_bx_tasks_txt_err_cannot_perform_action')]);
+
+            $iObjectId = (int)$aTrack['object_id'];
+            $iAuthorId = (int)$aTrack['author_id'];
+            if(($oTime = BxDolReport::getObjectInstance($CNF['OBJECT_REPORTS_TIME'], $iObjectId)) && $oTime->isEnabled()) {
+                $oTime->putReport($iObjectId, $iAuthorId, $aTrack, true); //--- Revoke old value
+                $oTime->putReport($iObjectId, $iAuthorId, $iTrack); //--- Process new value
+            }
+
+            return $this->_bIsApi ? [] : echoJson(['grid' => $this->getCode(false), 'blink' => $iTrack]);    
+        }
+
+        if($this->_bIsApi)
+            return $this->getFormBlockAPI($oForm, $sAction, $iLevel);
+
+        $sContent = BxTemplFunctions::getInstance()->popupBox($this->_oModule->_oConfig->getHtmlIds('time_popup'), _t('_bx_tasks_popup_title_time_edit'), $this->_oModule->_oTemplate->parseHtmlByName('popup_time.html', [
+            'form_id' => $oForm->getId(),
+            'form' => $oForm->getCode(true),
+            'object' => $this->_sObject,
+            'action' => $sAction
+        ]));
+
+        return echoJson(['popup' => ['html' => $sContent, 'options' => ['closeOnOuterClick' => false]]]);
+    }
+
+    protected function _delete ($mixedId)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aTrack = $this->_oModule->_oDb->getTimeTracks(['sample' => 'id', 'id' => (int)$mixedId]);
+        if(!is_array($aTrack) || empty($aTrack))
+            return false;
+
+        $iObjectId = (int)$aTrack['object_id'];
+        $iAuthorId = (int)$aTrack['author_id'];
+
+        $bResult = parent::_delete($mixedId);
+        if($bResult && ($oTime = BxDolReport::getObjectInstance($CNF['OBJECT_REPORTS_TIME'], $iObjectId)) && $oTime->isEnabled())
+            $oTime->putReport($iObjectId, $iAuthorId, $aTrack, true);
+
+        return $bResult;
+    }
+
+    protected function _getFormObject($sAction, $iId = 0)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $bActionAdd = $sAction == 'add';
+        $bActionEdit = $sAction == 'edit';
+
+        $aActionParams = [
+            'o' => $this->_sObject, 
+            'a' => $sAction,
+            'context_pid' => $this->_iContextPid
+        ];
+        if($bActionEdit && !empty($iId))
+            $aActionParams['id'] = $iId;
+
+        $sForm = $CNF['OBJECT_FORM_TIME_DISPLAY_' . strtoupper($sAction)];
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_TIME'], $sForm);
+        $oForm->setId($sForm);
+        $oForm->setName($sForm);
+    	$oForm->setAction(BX_DOL_URL_ROOT . bx_append_url_params('grid.php', $aActionParams));
+
+        $oForm->aParams['db']['table'] = $CNF['TABLE_TIME_TRACK'];
+        if($bActionAdd) {
+            $oForm->aInputs['sys']['value'] = $CNF['OBJECT_REPORTS_TIME'];
+            $oForm->aInputs['action']['value'] = 'Report';
+            $oForm->aInputs['object_id'] = array_merge($oForm->aInputs['object_id'], [
+                'type' => 'select',
+                'values' => [['key' => 0, 'value' => _t('_sys_please_select')]],
+                'required' => true,
+                'checker' => [
+                    'func' => 'Avail',
+                    'error' => _t('_bx_tasks_form_time_input_object_id_err')
+                ]
+            ]);
+
+            $aTasks = $this->_oModule->_oDb->getTasks(-$this->_iContextPid);
+            foreach($aTasks as $aTask) {
+                if($aTask[$CNF['FIELD_STATUS_ADMIN']] !== 'active')
+                    continue;
+
+                $oForm->aInputs['object_id']['values'][] = [
+                    'key' => $aTask[$CNF['FIELD_ID']],
+                    'value' => $aTask[$CNF['FIELD_TITLE']]
+                ];
+            }
+        }
+
+        return $oForm;
+    }
+
+    protected function _getFilterControls()
+    {
+        $this->__getFilterControls();
+
+        $sContent = $this->_getFilterSelectOne($this->_sFilter2Name, $this->_sFilter2Value, $this->_aFilter2Values);
+        $sContent .= $this->_getSearchInput();
+
+        return $sContent;
+    }
+
+    protected function _getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage)
+    {
+        $this->_aOptions['source'] .= $this->_oModule->_oDb->prepareAsString(" AND `ttt`.`author_id`=?", $this->_iLogged);
+
+        return parent::_getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
+    }
+}
+
+/** @} */

+ 289 - 0
modules/boonex/tasks/classes/BxTasksGridTimeAdministration.php

@@ -0,0 +1,289 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT 
+ * @defgroup    Tasks Tasks
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxTasksGridTimeAdministration extends BxTemplGrid
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    protected $_iContextPid;
+
+    protected $_sParamsDivider;
+    protected $_sFilter1Name;
+    protected $_sFilter1Value;
+    protected $_aFilter1Values;
+    protected $_sFilter2Name;
+    protected $_sFilter2Value;
+    protected $_aFilter2Values;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_tasks';
+    	$this->_oModule = BxDolModule::getInstance($this->_sModule);
+    	if(!$oTemplate)
+            $oTemplate = $this->_oModule->_oTemplate;
+
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_sDefaultSortingOrder = 'DESC';
+
+        if(($iContextPid = bx_get('context_pid')) !== false) 
+            $this->setContextPid($iContextPid);
+
+        $this->_sParamsDivider = '#-#';
+    }
+
+    public function setContextPid($iContextPid)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $this->_iContextPid = (int)$iContextPid;
+        $this->_aQueryAppend['context_pid'] = $this->_iContextPid;
+
+        /*
+         * Filter by author_id
+         */
+        $this->_sFilter1Name = 'filter1';
+        $this->_aFilter1Values = [];
+        $this->_sFilter1Value = '';
+
+        if($this->_sObject == $CNF['OBJECT_GRID_TIME_ADMINISTRATION']) {
+            if(($oContext = BxDolProfile::getInstance($this->_iContextPid)) !== false) {
+                $aPids = bx_srv($oContext->getModule(), 'fans', [$oContext->getContentId(), true]);
+                foreach($aPids as $iPid)
+                    $this->_aFilter1Values[$iPid] = $oContext->getDisplayName($iPid);
+            }
+
+            if(($sFilter1 = bx_get($this->_sFilter1Name)) !== false) {
+                $this->_sFilter1Value = bx_process_input($sFilter1);
+                $this->_aQueryAppend[$this->_sFilter1Name] = $this->_sFilter1Value;
+            }
+        }
+
+        /*
+         * Filter by object_id
+         */
+        $this->_sFilter2Name = 'filter2';
+        $this->_aFilter2Values = [];
+        $this->_sFilter2Value = '';
+
+        $aTasks = $this->_oModule->_oDb->getTasks(-$this->_iContextPid);
+        foreach($aTasks as $aTask)
+            $this->_aFilter2Values[$aTask[$CNF['FIELD_ID']]] = $aTask[$CNF['FIELD_TITLE']];
+
+        if(($sFilter2 = bx_get($this->_sFilter2Name)) !== false) {
+            $this->_sFilter2Value = bx_process_input($sFilter2);
+            $this->_aQueryAppend[$this->_sFilter2Name] = $this->_sFilter2Value;
+        }
+    }
+
+    public function performActionCalculate()
+    {
+        $sAction = 'calculate';
+
+        $aIds = $this->_getIds();
+        if($aIds === false)
+            return $this->_getActionResult([]);
+
+        $iTotal = 0;
+        foreach($aIds as $iId) {
+            $aTrack = $this->_oModule->_oDb->getTimeTracks(['sample' => 'id', 'id' => $iId]);
+            if(empty($aTrack) || !is_array($aTrack))
+                continue;
+
+            $iTotal += $aTrack['value'];
+        }
+
+        $sContent = BxTemplFunctions::getInstance()->transBox($this->_oModule->_oConfig->getHtmlIds('total_popup'), $this->_oModule->_oTemplate->parseHtmlByName('popup_total.html', [
+            'total' => $this->_oModule->_oConfig->timeI2S($iTotal)
+        ]));
+
+        return echoJson(['popup' => ['html' => $sContent, 'options' => ['closeOnOuterClick' => true]]]);
+    }
+
+    protected function _getCellAuthorId($mixedValue, $sKey, $aField, $aRow)
+    {
+        if($this->_bIsApi)
+            return ['type' => 'profile', 'data' => BxDolProfile::getData($mixedValue)];
+
+        if(($oProfile = BxDolProfile::getInstanceMagic($mixedValue)) !== false)
+            $mixedValue = $oProfile->getUnit();
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+    
+    protected function _getCellObjectId($mixedValue, $sKey, $aField, $aRow)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sTitle = $aRow[$CNF['FIELD_TITLE']];
+        if((int)$aField['chars_limit'] > 0)
+            $sTitle = strmaxtextlen($sTitle, (int)$aField['chars_limit']);
+        if($sTitle == '')
+            $sTitle = _t('_sys_txt_no_title');
+
+        $sUrl = bx_absolute_url(BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $mixedValue));
+        
+        if($this->_bIsApi)
+            return ['type' => 'link', 'data' => [
+                'text' => $aRow[$CNF['FIELD_TITLE']],
+                'url' => bx_api_get_relative_url($sUrl)
+            ]];
+        
+        return parent::_getCellDefault($this->_getEntryLink($sTitle, $sUrl, $aRow), $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellValue($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault($this->_oModule->_oConfig->timeI2S($mixedValue), $sKey, $aField, $aRow);
+    }
+    
+    protected function _getCellDate($mixedValue, $sKey, $aField, $aRow)
+    {
+        if($this->_bIsApi)
+            return ['type' => 'time', 'data' => $mixedValue];
+
+        return parent::_getCellDefault(bx_time_js($mixedValue, BX_FORMAT_DATE, true), $sKey, $aField, $aRow);
+    }
+
+    protected function _getActionEdit($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = [])
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if($this->_sObject == $CNF['OBJECT_GRID_TIME_ADMINISTRATION'])
+            $isDisabled = true;
+
+        return parent::_getActionDefault($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+    }
+
+    protected function _getActionDelete($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = [])
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if($this->_sObject == $CNF['OBJECT_GRID_TIME_ADMINISTRATION'])
+            $isDisabled = true;
+
+        return parent::_getActionDelete($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
+    }
+    
+    protected function _getFilterControls()
+    {
+        $this->__getFilterControls();
+
+        $sContent = $this->_getFilterSelectOne($this->_sFilter1Name, $this->_sFilter1Value, $this->_aFilter1Values);
+        $sContent .= $this->_getFilterSelectOne($this->_sFilter2Name, $this->_sFilter2Value, $this->_aFilter2Values);
+        $sContent .= $this->_getSearchInput();
+
+        return $sContent;
+    }
+
+    protected function _getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage)
+    {
+        $this->_aOptions['source'] .= $this->_oModule->_oDb->prepareAsString(" AND ABS(`tt`.`allow_view_to`)=?", $this->_iContextPid);
+
+        $aFilterParts = explode($this->_sParamsDivider, $sFilter);
+        switch (substr_count($sFilter, $this->_sParamsDivider)) {
+            case 1:
+                list($this->_sFilter1Value, $sFilter) = $aFilterParts;
+                break;
+
+            case 2:
+                list($this->_sFilter1Value, $this->_sFilter2Value, $sFilter) = $aFilterParts;
+                break;
+        }
+
+    	if(!empty($this->_sFilter1Value))
+            $this->_aOptions['source'] .= $this->_oModule->_oDb->prepareAsString(" AND `ttt`.`author_id`=?", $this->_sFilter1Value);
+
+        if(!empty($this->_sFilter2Value))
+            $this->_aOptions['source'] .= $this->_oModule->_oDb->prepareAsString(" AND `ttt`.`object_id`=?", $this->_sFilter2Value);
+
+        return parent::_getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
+    }
+
+    protected function _getFilterSelectOne($sFilterName, $sFilterValue, $aFilterValues, $bAddSelectOne = true)
+    {
+        if(empty($sFilterName))
+            return '';
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        $sJsObject = $this->_oModule->_oConfig->getJsObject('manage_tools');
+
+        $aInputValues = [];
+        if($bAddSelectOne)
+            $aInputValues[''] = _t($CNF['T']['filter_item_select_one_' . $sFilterName]);
+
+        foreach($aFilterValues as $sKey => $sValue)
+            $aInputValues[$sKey] = _t($sValue);
+
+        $aInputModules = [
+            'type' => 'select',
+            'name' => $sFilterName,
+            'attrs' => [
+                'id' => 'bx-grid-' . $sFilterName . '-' . $this->_sObject,
+                'onChange' => 'javascript:' . $sJsObject . '.onChangeFilter(this)'
+            ],
+            'value' => $sFilterValue,
+            'values' => $aInputValues
+        ];
+
+        $oForm = new BxTemplFormView([]);
+        return $oForm->genRow($aInputModules);
+    }
+
+    protected function _getSearchInput()
+    {
+        $sJsObject = $this->_oModule->_oConfig->getJsObject('manage_tools');
+
+        $aInputSearch = [
+            'type' => 'text',
+            'name' => 'search',
+            'attrs' => [
+                'id' => 'bx-grid-search-' . $this->_sObject,
+                'onKeyup' => 'javascript:$(this).off(\'keyup focusout\'); ' . $sJsObject . '.onChangeFilter(this)',
+                'onBlur' => 'javascript:' . $sJsObject . '.onChangeFilter(this)',
+            ]
+        ];
+
+        $oForm = new BxTemplFormView([]);
+        return $oForm->genRow($aInputSearch);
+    }
+
+    protected function _getEntryLink($sTitle, $sUrl, $aRow)
+    {
+        return $this->_oTemplate->parseHtmlByName('title_link.html', [
+            'href' => $sUrl,
+            'title' => bx_html_attribute($sTitle),
+            'content' => bx_process_output($sTitle),
+            'target' => '_blank'
+        ]);
+    }
+
+    protected function __getFilterControls()
+    {
+        parent::_getFilterControls();
+    }
+
+    protected function _getIds()
+    {
+        $aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds)) {
+            $iId = (int)bx_get('id');
+            if(!$iId) 
+                return false;
+
+            $aIds = [$iId];
+        }
+
+        return $aIds;
+    }
+}
+
+/** @} */

+ 9 - 0
modules/boonex/tasks/classes/BxTasksMenuViewActions.php

@@ -38,6 +38,15 @@ class BxTasksMenuViewActions extends BxBaseModTextMenuViewActions
     {
         return $this->_getMenuItemByNameActions($aItem);
     }
+
+    protected function _getMenuItemReportTime($aItem, $aParams = [])
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        return parent::_getMenuItemReport($aItem, array_merge($aParams, [
+            'object' => $CNF['OBJECT_REPORTS_TIME']
+        ]));
+    }
 }
 
 /** @} */

+ 128 - 70
modules/boonex/tasks/classes/BxTasksModule.php

@@ -100,72 +100,74 @@ class BxTasksModule extends BxBaseModTextModule implements iBxDolCalendarService
         echo 'ok';
     }
 	
-	public function actionSetFilterValue($iListId, $sValue)
-	{
-		$CNF = &$this->_oConfig->CNF;
-		$aTmp = array();
-		if (isset($_COOKIE[$CNF['COOKIE_SETTING_KEY']]))
-			$aTmp =	json_decode($_COOKIE[$CNF['COOKIE_SETTING_KEY']], true);
-			
-		if ($sValue != '')
-			$aTmp[$iListId] = $sValue;
-		else
-			unset($aTmp[$iListId]);
+    public function actionSetFilterValue($iListId, $sValue)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aTmp = array();
+        if (isset($_COOKIE[$CNF['COOKIE_SETTING_KEY']]))
+            $aTmp = json_decode($_COOKIE[$CNF['COOKIE_SETTING_KEY']], true);
+
+        if ($sValue != '')
+            $aTmp[$iListId] = $sValue;
+        else
+            unset($aTmp[$iListId]);
+
         bx_setcookie($CNF['COOKIE_SETTING_KEY'], json_encode($aTmp), time() + 60*60*24*365);
-	}
+    }
 	
-	public function actionProcessTaskListForm($iContextId, $iId)
+    public function actionProcessTaskListForm($iContextId, $iId)
     {
         if (!$this->isAllowAdd(-$iContextId))
             return;
-        
-		$CNF = &$this->_oConfig->CNF;
-		$oForm = null;
-		$sPopupTitle = "";
-		$aContentInfo = array();
-		if ($iId == 0){
-			$oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_LIST_ENTRY'], $CNF['OBJECT_FORM_LIST_ENTRY_DISPLAY_ADD']);
-			$sPopupTitle = _t('_bx_tasks_form_list_entry_display_add');
-		}
-		else{
-			$oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_LIST_ENTRY'], $CNF['OBJECT_FORM_LIST_ENTRY_DISPLAY_EDIT']);
-			$aContentInfo = $this->_oDb->getList($iId);
-			$sPopupTitle = _t('_bx_tasks_form_list_entry_display_edit');
-		}
-		
-		$oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . $this->_oConfig->getBaseUri() . 'process_task_list_form/' . $iContextId . '/' . $iId . '/';
-		if (!$oForm)
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $oForm = null;
+        $sPopupTitle = "";
+        $aContentInfo = array();
+        if ($iId == 0){
+            $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_LIST_ENTRY'], $CNF['OBJECT_FORM_LIST_ENTRY_DISPLAY_ADD']);
+            $sPopupTitle = _t('_bx_tasks_form_list_entry_display_add');
+        }
+        else {
+            $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_LIST_ENTRY'], $CNF['OBJECT_FORM_LIST_ENTRY_DISPLAY_EDIT']);
+            $aContentInfo = $this->_oDb->getList($iId);
+            $sPopupTitle = _t('_bx_tasks_form_list_entry_display_edit');
+        }
+
+        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . $this->_oConfig->getBaseUri() . 'process_task_list_form/' . $iContextId . '/' . $iId . '/';
+        if (!$oForm)
             return '';
 		
-		$oForm->initChecker($aContentInfo, array());
-		
+        $oForm->initChecker($aContentInfo, array());
         if($oForm->isSubmittedAndValid()) {
-			if ($iId == 0){
-				$aValsToAdd['context_id'] = $iContextId;
-				$iId = $oForm->insert($aValsToAdd);
-			}
-			else{
-				$iId = $oForm->update($iId);
-			}
-
-			return echoJson(array(
-				 'eval' => $this->_oConfig->getJsObject('tasks') . '.reloadData(oData, ' . $iContextId . ')',
-			));
+            if ($iId == 0){
+                $aValsToAdd['context_id'] = $iContextId;
+                $iId = $oForm->insert($aValsToAdd);
+            }
+            else {
+                $iId = $oForm->update($iId);
+            }
+
+            return echoJson(array(
+                'eval' => $this->_oConfig->getJsObject('tasks') . '.reloadData(oData, ' . $iContextId . ')',
+            ));
         }
         else {	
-			$sContent = $this->_oTemplate->parseHtmlByName('popup_form.html', array(
-				'form_id' => $oForm->getId(),
-				'form' => $oForm->getCode(true)
-			));
-																	 
-			if (!$oForm->isSubmitted()){
-				echo $sContent;
-				return;
-			}
-			
-            return echoJson(array('form' => $sContent, 'form_id' => $oForm->getId()));;
+            $sContent = $this->_oTemplate->parseHtmlByName('popup_form.html', array(
+                    'form_id' => $oForm->getId(),
+                    'form' => $oForm->getCode(true)
+            ));
+
+            if (!$oForm->isSubmitted()) {
+                    echo $sContent;
+                    return;
+            }
+
+            return echoJson(array('form' => $sContent, 'form_id' => $oForm->getId()));
         }
-	}
+    }
     
     public function actionDeleteTaskList($iId, $iContextId)
     {
@@ -267,22 +269,70 @@ class BxTasksModule extends BxBaseModTextModule implements iBxDolCalendarService
         $aSQLPart = array();
         $iContextId = (int)bx_get('context_id');
         
-        if (!$this->isAllowView($iContextId))
+        if(!$this->isAllowView($iContextId))
             return; 
 		
-		$oPrivacy = BxDolPrivacy::getObjectInstance($this->_oConfig->CNF['OBJECT_PRIVACY_VIEW']);
-		
-		if($iContextId){
-			$aSQLPart = $oPrivacy ? $oPrivacy->getContentByGroupAsSQLPart(- $iContextId) : array();
-		}
+        $oPrivacy = BxDolPrivacy::getObjectInstance($this->_oConfig->CNF['OBJECT_PRIVACY_VIEW']);
+
+        if($iContextId) {
+            $aSQLPart = $oPrivacy ? $oPrivacy->getContentByGroupAsSQLPart(- $iContextId) : array();
+        }
+
         // get entries
         $aEntries = $this->_oDb->getEntriesByDate(bx_get('start'), bx_get('end'), bx_get('event'), $aSQLPart);
         
         header('Content-Type: application/json; charset=utf-8');
         echo json_encode($aEntries);
     }
-	
-	/**
+
+    public function serviceGetBlockMenuContext($iProfileId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $oMenu = BxDolMenu::getObjectInstance($CNF['OBJECT_MENU_SUBMENU_VIEW_CONTEXT']);
+        if(!$oMenu)
+            return '';
+
+        $oMenu->addMarkers(['profile_id' => $iProfileId]);
+        return $oMenu->getCode();
+    }
+
+    public function serviceGetBlockManageTimeContext($iContextPid)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        return $this->_getBlockManageTimeContext($CNF['OBJECT_GRID_TIME'], $iContextPid);
+    }
+    
+    public function serviceGetBlockAdministrateTimeContext($iContextPid)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        return $this->_getBlockManageTimeContext($CNF['OBJECT_GRID_TIME_ADMINISTRATION'], $iContextPid);
+    }
+
+    protected function _getBlockManageTimeContext($sGridObject, $iContextPid)
+    {
+        $oGrid = BxDolGrid::getObjectInstance($sGridObject);
+        if(!$oGrid)
+            return $this->_bIsApi ? [] : '';
+
+        $oGrid->setContextPid($iContextPid);
+
+        if($this->_bIsApi)
+            return [
+                bx_api_get_block('grid', $oGrid->getCodeAPI())
+            ];
+
+        $this->_oTemplate->addCss(['manage_tools.css']);
+        $this->_oTemplate->addJs(['modules/base/text/js/|manage_tools.js', 'manage_tools.js']);
+        $this->_oTemplate->addJsTranslation(['_sys_grid_search']);
+        return $this->_oTemplate->getJsCode('manage_tools', [
+            'sObjNameGrid' => $sGridObject
+        ]) . $oGrid->getCode();
+    }
+
+    /**
      * Data for Timeline module
      */
     public function serviceGetTimelineData()
@@ -291,21 +341,21 @@ class BxTasksModule extends BxBaseModTextModule implements iBxDolCalendarService
         return array(
             'handlers' => array(
                 array('group' => $sModule . '_object', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'added', 'module_name' => $sModule, 'module_method' => 'get_timeline_post', 'module_class' => 'Module', 'groupable' => 0, 'group_by' => ''),
-				array('group' => $sModule . '_completed', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'completed', 'module_name' => $sModule, 'module_method' => 'get_timeline_completed', 'module_class' => 'Module',  'groupable' => 0, 'group_by' => ''),
-				array('group' => $sModule . '_reopened', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'reopened', 'module_name' => $sModule, 'module_method' => 'get_timeline_reopened', 'module_class' => 'Module',  'groupable' => 0, 'group_by' => ''),
+                array('group' => $sModule . '_completed', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'completed', 'module_name' => $sModule, 'module_method' => 'get_timeline_completed', 'module_class' => 'Module',  'groupable' => 0, 'group_by' => ''),
+                array('group' => $sModule . '_reopened', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'reopened', 'module_name' => $sModule, 'module_method' => 'get_timeline_reopened', 'module_class' => 'Module',  'groupable' => 0, 'group_by' => ''),
                 array('group' => $sModule . '_object', 'type' => 'update', 'alert_unit' => $sModule, 'alert_action' => 'edited'),
                 array('group' => $sModule . '_object', 'type' => 'delete', 'alert_unit' => $sModule, 'alert_action' => 'deleted'),
             ),
             'alerts' => array(
                 array('unit' => $sModule, 'action' => 'added'),
-				array('unit' => $sModule, 'action' => 'completed'),
-				array('unit' => $sModule, 'action' => 'reopened'),
+                array('unit' => $sModule, 'action' => 'completed'),
+                array('unit' => $sModule, 'action' => 'reopened'),
                 array('unit' => $sModule, 'action' => 'edited'),
                 array('unit' => $sModule, 'action' => 'deleted'),
             )
         );
     }
-	
+
     /**
      * Entry task for Timeline module
      */
@@ -434,12 +484,20 @@ class BxTasksModule extends BxBaseModTextModule implements iBxDolCalendarService
         return $aResult;
     }
 
+    public function serviceCheckAllowedManageInContext($iContextPid)
+    {
+        if(!$this->isAllowManageByContext($iContextPid))
+            return false;
+
+        return true;
+    }
+
     public function serviceCheckAllowedManage($iContentId)
     {
         if(!$this->isAllowManage($iContentId))
             return false;
 
-        return true; 
+        return true;
     }
 
     public function serviceCheckAllowedComplete($iContentId)
@@ -601,7 +659,7 @@ class BxTasksModule extends BxBaseModTextModule implements iBxDolCalendarService
 
         return $this->_oTemplate->getEntriesList($iContextId);
     }
-	
+
     /**
      * Common methods
      */

+ 9 - 1
modules/boonex/tasks/classes/BxTasksPageAuthor.php

@@ -13,11 +13,19 @@
  */
 class BxTasksPageAuthor extends BxBaseModTextPageAuthor
 {
+    protected $_sModule;
+    protected $_oModule;
+
     public function __construct($aObject, $oTemplate = false)
     {
         parent::__construct($aObject, $oTemplate);
-    }
 
+        $this->_sModule = 'bx_tasks';
+    	$this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        if(($oMenuHolder = BxDolMenu::getObjectInstance('sys_site_submenu')) && ($oMenu = BxDolMenu::getObjectInstance($oMenuHolder->getObjectSubmenu())))
+            $oMenu->setSelected($this->_sModule, 'tasks-context');
+    }
 }
 
 /** @} */

+ 20 - 15
modules/boonex/tasks/classes/BxTasksTemplate.php

@@ -60,21 +60,26 @@ class BxTasksTemplate extends BxBaseModTextTemplate
 
         $aLists = $this->_oDb->getLists($iContextId);
 
-        $aListsVars = array();
+        $aTmplVarsLists = [];
         foreach($aLists as $aList) {
-            $aTasks = $this->_oDb->getTasks($iContextId, $aList['id']);
-            $aTasksVars = array();
+            $aTasks = $this->_oDb->getTasks($iContextId, $aList['id'], true);
+
+            $aTmplVarsTasks = [];
             foreach($aTasks as $aTask) {
+                $sState = $aStates[$aTask[$CNF['FIELD_STATE']]] ?? '';
+                if(!empty($aTask['time']))
+                    $sState .= ' ' . $this->_oConfig->timeI2S($aTask['time']);
+                if($bAllowManage && !empty($aTask['time_total']))
+                    $sState .= ' (' . $this->_oConfig->timeI2S($aTask['time_total']) . ')';
+
                 $aMembers = $oConnection->getConnectedInitiators($aTask[$CNF['FIELD_ID']]);
 
-                $aMembersVars = array();
-                foreach($aMembers as $iMember) {
-                    $oProfile = BxDolProfile::getInstance($iMember);
-                    if($oProfile && !($oProfile instanceof BxDolProfileUndefined))
-                        $aMembersVars[] = array('info' => $oProfile->getUnit(0, array('template' => 'unit_wo_info')));
-                }
+                $aTmplVarsMembers = [];
+                foreach($aMembers as $iMember)
+                    if(($oProfile = BxDolProfile::getInstance($iMember)) !== false && !($oProfile instanceof BxDolProfileUndefined))
+                        $aTmplVarsMembers[] = array('info' => $oProfile->getUnit(0, array('template' => 'unit_wo_info')));
 
-                $aTasksVars[] = array(
+                $aTmplVarsTasks[] = array(
                     'id' => $aTask[$CNF['FIELD_ID']],
                     'title' => bx_process_output($aTask[$CNF['FIELD_TITLE']]),
                     'created' => bx_time_js($aTask[$CNF['FIELD_ADDED']]),
@@ -82,8 +87,8 @@ class BxTasksTemplate extends BxBaseModTextTemplate
                     'due' => $aTask[$CNF['FIELD_DUEDATE']] > 0 ? bx_time_js($aTask[$CNF['FIELD_DUEDATE']]) : '',
                     'type' => $aTypes[$aTask[$CNF['FIELD_TYPE']]] ?? '',
                     'priority' => $aPriorities[$aTask[$CNF['FIELD_PRIORITY']]] ?? '',
-                    'state' => $aStates[$aTask[$CNF['FIELD_STATE']]] ?? '',
-                    'bx_repeat:members' => $aMembersVars,
+                    'state' => $sState,
+                    'bx_repeat:members' => $aTmplVarsMembers,
                     'badges' => $oModule->serviceGetBadges($aTask[$CNF['FIELD_ID']], true),
                     'url' => bx_absolute_url($oPermalinks->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $aTask[$CNF['FIELD_ID']])),
                     'object' => $this->_oConfig->getJsObject('tasks'),
@@ -114,7 +119,7 @@ class BxTasksTemplate extends BxBaseModTextTemplate
                     $sAll = 'selected';
             }
 
-            $aListsVars[] = array(
+            $aTmplVarsLists[] = array(
                 'bx_if:allow_edit_list' => array(
                     'condition' => $bAllowAdd,
                     'content' => array(
@@ -147,7 +152,7 @@ class BxTasksTemplate extends BxBaseModTextTemplate
                     )
                 ),
                 'id' => $aList['id'],
-                'bx_repeat:tasks' =>  $aTasksVars,
+                'bx_repeat:tasks' =>  $aTmplVarsTasks,
                 'context_id' => $iContextId,
                 'list_id' => $aList[$CNF['FIELD_ID']],
                 'object' => $this->_oConfig->getJsObject('tasks'),
@@ -165,7 +170,7 @@ class BxTasksTemplate extends BxBaseModTextTemplate
         ]);
 
         return $this->getJsCode('tasks', ['t_confirm_block_deletion' => _t('_bx_tasks_confirm_tasklist_deletion')]) . $this->parseHtmlByName('browse_tasks.html', [
-            'bx_repeat:task_lists' => $aListsVars,
+            'bx_repeat:task_lists' => $aTmplVarsLists,
             'bx_if:allow_add_list' => [
                 'condition' => $bAllowAdd,
                 'content' => [

+ 180 - 0
modules/boonex/tasks/classes/BxTasksTime.php

@@ -0,0 +1,180 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT 
+ * @defgroup    Tasks Tasks
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+require_once('BxTasksTimeQuery.php');
+
+class BxTasksTime extends BxTemplReport
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    public function __construct($sSystem, $iId, $iInit = true, $oTemplate = false)
+    {
+        parent::__construct($sSystem, $iId, $iInit, $oTemplate);
+
+        $this->_sModule = 'bx_tasks';
+    	$this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $this->_oQuery = new BxTasksTimeQuery($this);
+
+        $this->_bUndo = false;
+
+        $this->_sFormObject = $CNF['OBJECT_FORM_TIME'];
+        $this->_sFormDisplayPost = $CNF['OBJECT_FORM_TIME_DISPLAY_ADD'];
+
+        array_walk($this->_aHtmlIds, function(&$sVa1ue) {
+            $sVa1ue = str_replace('bx-report', 'bx-tasks', $sVa1ue);
+        });
+    }
+
+    /**
+     * Should always return false to allow any number of time reports per user.
+     */
+    public function isPerformed($iObjectId, $iAuthorId, $iAuthorIp = 0)
+    {
+        return false;
+    }
+
+    public function putReport($iObjectId, $iAuthorId, $mixedTrack, $bUndo = false)
+    {
+        if(!$this->_oQuery->putReport($iObjectId, $iAuthorId, $bUndo))
+            return false;
+
+        $aTrack = is_array($mixedTrack) ? $mixedTrack : $this->_oQuery->getTrackBy(['type' => 'id', 'id' => (int)$mixedTrack]);
+        if(empty($aTrack) || !is_array($aTrack))
+            return false;
+
+        if(!$this->_oQuery->updateReport($iObjectId, $aTrack['value'], $bUndo))
+            return false;
+
+        $iObjectAuthorId = $this->_oQuery->getObjectAuthorId($iObjectId);
+
+        /*
+         * TODO: May be we need to send notification to Context admins.
+         * 
+        $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('t_Reported', [
+           'report_type' => $sType,
+           'report_text' => $sText,
+           'report_url' => $this->getBaseUrl(),
+        ]);
+        if($aTemplate)
+           sendMail(getParam('site_email'), $aTemplate['Subject'], $aTemplate['Body']);
+        */          
+
+        $this->_trigger();
+
+        /**
+         * @hooks
+         * @hookdef bx_tasks-report_time '{module_name}', 'report_time' - hook on create new time report 
+         * - $unit_name - module name
+         * - $action - equals `report_time` 
+         * - $object_id - reported entry ID
+         * - $sender_id - profile id for report's author
+         * - $extra_params - array of additional params with the following array keys:
+         *      - `object_system` - [string] system name
+         *      - `object_author_id` - [int] author's profile_id for reported object_id 
+         *      - `report_id` - [int] report id
+         *      - `report_author_id` - [int] profile id for report's author
+         *      - `type` - [string] reported time
+         * @hook @ref hook-bx_tasks-report_time
+         */
+        bx_alert($this->_sModule, 'report_time', $iObjectId, $iAuthorId, [
+            'object_system' => $this->_sSystem, 
+            'object_author_id' => $iObjectAuthorId, 
+            'report_id' => $aTrack['id'], 
+            'report_author_id' => $iAuthorId, 
+            'value' => $aTrack['value']
+        ]);
+
+        return true;
+    }
+
+    protected function _report($bPerformed, $aParams, &$oForm)
+    {
+        $iAuthorId = $this->_getAuthorId();
+        $iAuthorNip = bx_get_ip_hash($this->_getAuthorIp());
+
+        $iObjectId = $this->_bApi ? $this->_iId : $oForm->getCleanValue('object_id');
+
+        if(!$this->isAllowedReport(true))
+            return ['code' => 2, 'message' => $this->msgErrAllowedReport()];
+
+        $sValue = $this->_bApi ? $aParams['value'] : $oForm->getCleanValue('value');
+        $iValue = $this->_oModule->_oConfig->timeS2I($sValue);
+
+        $sText = $this->_bApi ? $aParams['text'] : $oForm->getCleanValue('text');
+        $sText = bx_process_input($sText, BX_DATA_TEXT_MULTILINE);
+
+        $iId = (int)$oForm->insert(['object_id' => $iObjectId, 'author_id' => $iAuthorId, 'author_nip' => $iAuthorNip, 'value' => $iValue,  'text' => $sText,  'date' => time()]);
+        if($iId != 0 && $this->putReport($iObjectId, $iAuthorId, $iId)) {
+            $aReport = $this->_getReport($iObjectId, true);
+            $aResult = $this->_returnReportData($iObjectId, $iAuthorId, $iId, $aReport, !$bPerformed);
+
+            if(($oSockets = BxDolSockets::getInstance()) && $oSockets->isEnabled())
+                $oSockets->sendEvent($this->getSocketName(), $iObjectId, 'reported', json_encode($this->_returnReportDataForSocket($aResult)));
+
+            return $aResult;
+        }
+
+        return ['code' => 3, 'message' => _t('_report_err_cannot_perform_action')];
+    }
+
+    protected function _returnReportData($iObjectId, $iAuthorId, $iReportId, $aData, $bPerformed)
+    {
+        return parent::_returnReportData($iObjectId, $iAuthorId, $iReportId, $aData, false);
+    }
+
+    protected function _getReportedBy()
+    {
+        $aTmplReports = [];
+
+        $aReports = $this->_oQuery->getPerformedBy($this->getId());
+        foreach($aReports as $aReport) {
+            list($sUserName, $sUserUrl, $sUserIcon, $sUserUnit) = $this->_getAuthorInfo($aReport['author_id']);
+
+            $sText = bx_process_output($aReport['text'], BX_DATA_TEXT_MULTILINE);
+
+            $aTmplReports[] = [
+                'style_prefix' => $this->_sStylePrefix,
+                'user_unit' => $sUserUnit,
+                'value' => $this->_oModule->_oConfig->timeI2S($aReport['value']),
+                'date' => bx_time_js($aReport['date'], BX_FORMAT_DATE, true),
+            	'bx_if:show_text' => [
+                    'condition' => strlen($sText) > 0,
+                    'content' => [
+                        'text' => $sText
+                    ]
+            	]
+            ];
+        }
+
+        if(empty($aTmplReports))
+            $aTmplReports = MsgBox(_t('_Empty'));
+
+        return $this->_oModule->_oTemplate->parseHtmlByName('report_by_list.html', [
+            'style_prefix' => $this->_sStylePrefix,
+            'bx_repeat:list' => $aTmplReports
+        ]);
+    }
+
+    protected function _getIconDoReport($bPerformed)
+    {
+    	return $bPerformed && $this->isUndo() ? 'stopwatch' : 'stopwatch';
+    }
+
+    protected function _getTitleDoReport($bPerformed)
+    {
+        return ['_bx_tasks_report_time_do_' . ($bPerformed && $this->isUndo() ? 'un' : '') . 'report'];
+    }
+}
+
+/** @} */

+ 54 - 0
modules/boonex/tasks/classes/BxTasksTimeQuery.php

@@ -0,0 +1,54 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT 
+ * @defgroup    Tasks Tasks
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxTasksTimeQuery extends BxDolReportQuery
+{
+    public function __construct(&$oModule)
+    {
+        parent::__construct($oModule);
+    }
+
+    public function getReport($iObjectId)
+    {
+        $aResult = $this->getRow("SELECT `count` as `count`, `sum` AS `sum` FROM `{$this->_sTable}` WHERE `object_id` = :object_id LIMIT 1", ['object_id' => $iObjectId]);
+        if(empty($aResult) || !is_array($aResult))
+            $aResult = ['count' => 0, 'sum' => 0];
+
+        return $aResult;
+    }
+
+    public function updateReport($iObjectId, $iValue, $bUndo = false)
+    {
+        return (int)$this->query("UPDATE `{$this->_sTable}` SET `sum` = `sum` " . ($bUndo ? "-" : "+") . " :value WHERE `object_id` = :object_id", [
+            'object_id' => $iObjectId,
+            'value' => $iValue
+        ]) != 0;
+    }
+
+    /**
+     * Note. Tracks shouldn't be deleted on Undo.
+     * 
+     * @see BxDolReportQuery::putReport
+     */
+    protected function _deleteTrack($iObjectId, $iAuthorId)
+    {
+        return true;
+    }
+
+    protected function _updateTriggerTable($iObjectId, $aEntry)
+    {
+        return (int)$this->query("UPDATE `{$this->_sTriggerTable}` SET `{$this->_sTriggerFieldCount}` = :field_count WHERE `{$this->_sTriggerFieldId}` = :field_id", [
+            'field_count' => $aEntry['sum'],
+            'field_id' => $iObjectId
+        ]) > 0;
+    }
+}
+
+/** @} */

+ 2 - 5
modules/boonex/tasks/install/config.php

@@ -42,15 +42,12 @@ $aConfig = array(
     /**
      * List of page triggers.
      */
-    'page_triggers' => array (
-    	'trigger_page_profile_view_entry',
-    ),
+    'page_triggers' => array (),
     
     /**
      * Menu triggers.
      */
     'menu_triggers' => array(
-        'trigger_profile_view_submenu',
         'trigger_group_view_submenu',
     ),
 
@@ -115,7 +112,7 @@ $aConfig = array(
     ),
     'enable_success' => array(
         'process_menu_triggers' => 1,
-        'process_page_triggers' => 1,
+        'process_page_triggers' => 0,
     	'process_esearches' => 1,
         'register_transcoders' => 1,
         'clear_db_cache' => 1,

+ 60 - 21
modules/boonex/tasks/install/langs/en.xml

@@ -13,7 +13,8 @@
     <string name="_bx_tasks_option_rss_num"><![CDATA[Number of items in RSS feed]]></string>
     <string name="_bx_tasks_option_searchable_fields"><![CDATA[Searchable fields for keyword search]]></string>
     <string name="_bx_tasks_option_auto_activation_for_categories"><![CDATA[Auto-Activation For Categories]]></string>
-  
+
+    <!-- Forms -->
     <string name="_bx_tasks_form_entry"><![CDATA[Tasks]]></string>
     <string name="_bx_tasks_form_entry_display_add"><![CDATA[Add Task]]></string>
     <string name="_bx_tasks_form_entry_display_edit"><![CDATA[Edit Task]]></string>
@@ -76,13 +77,14 @@
     <string name="_bx_tasks_form_entry_input_sys_attachments"><![CDATA[Attachments]]></string>
     <string name="_bx_tasks_form_entry_input_sys_allow_comments"><![CDATA[Allow comments]]></string>
     <string name="_bx_tasks_form_entry_input_allow_comments"><![CDATA[Allow comments]]></string>
-	
     <string name="_bx_tasks_form_entry_input_sys_tasks_list"><![CDATA[Task list]]></string>
     <string name="_bx_tasks_form_entry_input_tasks_list"><![CDATA[Task list]]></string>
-    <string name="_bx_tasks_form_profile_input_sys_initial_members"><![CDATA[Assign to]]></string>
-    <string name="_bx_tasks_form_profile_input_initial_members"><![CDATA[Assign to]]></string>
+    <string name="_bx_tasks_form_entry_input_sys_initial_members"><![CDATA[Assign to]]></string>
+    <string name="_bx_tasks_form_entry_input_initial_members"><![CDATA[Assign to]]></string>
     <string name="_bx_tasks_form_entry_input_sys_date_due_date"><![CDATA[Due date]]></string>
     <string name="_bx_tasks_form_entry_input_date_due_date"><![CDATA[Due date]]></string>
+    <string name="_bx_tasks_form_entry_input_sys_estimate"><![CDATA[Estimate]]></string>
+    <string name="_bx_tasks_form_entry_input_estimate"><![CDATA[Estimate (in hours)]]></string>
 
     <string name="_bx_tasks_form_list_entry"><![CDATA[Tasklists]]></string>
     <string name="_bx_tasks_form_list_entry_display_add"><![CDATA[Add Tasklist]]></string>
@@ -93,6 +95,23 @@
     <string name="_bx_tasks_form_list_entry_input_title"><![CDATA[Title]]></string>
     <string name="_bx_tasks_form_list_entry_input_title_err"><![CDATA[Title is essential. Please, fill in this field]]></string>
 
+    <string name="_bx_tasks_form_time"><![CDATA[Time]]></string>
+    <string name="_bx_tasks_form_display_time_add"><![CDATA[Add Time]]></string>
+    <string name="_bx_tasks_form_display_time_edit"><![CDATA[Edit Time]]></string>
+    <string name="_bx_tasks_form_time_input_sys_sys"><![CDATA[System]]></string>
+    <string name="_bx_tasks_form_time_input_sys_object_id"><![CDATA[Object ID]]></string>
+    <string name="_bx_tasks_form_time_input_object_id"><![CDATA[Task]]></string>
+    <string name="_bx_tasks_form_time_input_object_id_err"><![CDATA[This field in essential. Please, fill it in.]]></string>
+    <string name="_bx_tasks_form_time_input_sys_action"><![CDATA[Action]]></string>
+    <string name="_bx_tasks_form_time_input_sys_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_tasks_form_time_input_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_tasks_form_time_input_sys_value"><![CDATA[Time]]></string>
+    <string name="_bx_tasks_form_time_input_value"><![CDATA[Time]]></string>
+    <string name="_bx_tasks_form_time_input_sys_text"><![CDATA[Note]]></string>
+    <string name="_bx_tasks_form_time_input_text"><![CDATA[Note]]></string>
+    <string name="_bx_tasks_popup_title_time_add"><![CDATA[Add Time]]></string>
+    <string name="_bx_tasks_popup_title_time_edit"><![CDATA[Edit Time]]></string>
+
     <string name="_bx_tasks_category_form_entry"><![CDATA[Categories Form]]></string>
     <string name="_bx_tasks_category_display_add"><![CDATA[Add category]]></string>
     <string name="_bx_tasks_category_display_edit"><![CDATA[Edit category]]></string>
@@ -101,6 +120,7 @@
     <string name="_bx_tasks_category_form_entry_input_value_err"><![CDATA[Value is essential. Please, fill in this field]]></string>
     <string name="_bx_tasks_lmi_cpt_categories"><![CDATA[Categories]]></string>
 
+    <!-- ACL -->
     <string name="_bx_tasks_acl_action_create_entry"><![CDATA[Create Task]]></string>
     <string name="_bx_tasks_acl_action_delete_entry"><![CDATA[Delete Task]]></string>
     <string name="_bx_tasks_acl_action_view_entry"><![CDATA[View Task]]></string>
@@ -108,6 +128,7 @@
     <string name="_bx_tasks_acl_action_edit_any_entry"><![CDATA[Edit Any Task]]></string>
     <string name="_bx_tasks_acl_action_delete_any_entry"><![CDATA[Delete Any Task]]></string>
 
+    <!-- Pages -->
     <string name="_bx_tasks_page_title_sys_create_entry"><![CDATA[New Task]]></string>
     <string name="_bx_tasks_page_title_create_entry"><![CDATA[New Task]]></string>
     <string name="_bx_tasks_page_block_title_create_entry"><![CDATA[New Task]]></string>
@@ -199,7 +220,18 @@
     <string name="_bx_tasks_page_block_title_calendar_in_context"><![CDATA[Calendar]]></string>
     <string name="_bx_tasks_page_block_title_sys_calendar_of_author"><![CDATA[Calendar]]></string>
     <string name="_bx_tasks_page_block_title_calendar_of_author"><![CDATA[Calendar]]></string>
+    <string name="_bx_tasks_page_title_sys_entries_time_in_context"><![CDATA[My time in context]]></string>
+    <string name="_bx_tasks_page_title_entries_time_in_context"><![CDATA[My time in {display_name}]]></string>
+    <string name="_bx_tasks_page_title_sys_entries_time_in_context_administration"><![CDATA[All time in context]]></string>
+    <string name="_bx_tasks_page_title_entries_time_in_context_administration"><![CDATA[All time in {display_name}]]></string>
+    <string name="_bx_tasks_page_block_title_sys_menu_in_context"><![CDATA[View context menu]]></string>
+    <string name="_bx_tasks_page_block_title_menu_in_context"><![CDATA[Menu]]></string>
+    <string name="_bx_tasks_page_block_title_sys_entries_time_in_context"><![CDATA[My time in context]]></string>
+    <string name="_bx_tasks_page_block_title_entries_time_in_context"><![CDATA[My Time in <a href="{profile_link}">{display_name}</a>]]></string>
+    <string name="_bx_tasks_page_block_title_sys_entries_time_in_context_administration"><![CDATA[All time in context]]></string>
+    <string name="_bx_tasks_page_block_title_entries_time_in_context_administration"><![CDATA[All Time in <a href="{profile_link}">{display_name}</a>]]></string>
 
+    <!-- Menus -->
     <string name="_bx_tasks_menu_title_entry_attachments"><![CDATA[Form Attachments]]></string>
     <string name="_bx_tasks_menu_set_title_entry_attachments"><![CDATA[Form Attachments]]></string>
     <string name="_bx_tasks_menu_item_title_system_cpa_photo_simple"><![CDATA[Add Photo (simple)]]></string>
@@ -216,7 +248,6 @@
     <string name="_bx_tasks_menu_item_title_cpa_file_simple"><![CDATA[Add File]]></string>
     <string name="_bx_tasks_menu_item_title_system_cpa_file_html5"><![CDATA[Add File (html5)]]></string>
     <string name="_bx_tasks_menu_item_title_cpa_file_html5"><![CDATA[Add Files]]></string>
-
     <string name="_bx_tasks_menu_set_title_view_entry"><![CDATA[Task Actions]]></string>
     <string name="_bx_tasks_menu_title_view_entry"><![CDATA[Task Actions]]></string>
     <string name="_bx_tasks_menu_title_submenu"><![CDATA[Tasks Submenu]]></string>
@@ -263,27 +294,32 @@
     <string name="_bx_tasks_menu_item_title_admt_tasks"><![CDATA[Tasks]]></string>
     <string name="_bx_tasks_menu_item_title_manage_my"><![CDATA[My Tasks]]></string>
     <string name="_bx_tasks_menu_item_title_manage_all"><![CDATA[All Tasks]]></string>
-
+    <string name="_bx_tasks_menu_item_title_system_report_time"><![CDATA[Report Time]]></string>
     <string name="_bx_tasks_menu_item_title_system_set_completed"><![CDATA[Complete]]></string>
     <string name="_bx_tasks_menu_item_title_set_completed"><![CDATA[Complete]]></string>
     <string name="_bx_tasks_menu_item_title_system_set_uncompleted"><![CDATA[Re-open]]></string>
     <string name="_bx_tasks_menu_item_title_set_uncompleted"><![CDATA[Re-open]]></string>
+    <string name="_bx_tasks_menu_title_view_context_submenu"><![CDATA[View Context Submenu]]></string>
+    <string name="_bx_tasks_menu_set_title_view_context_submenu"><![CDATA[View Context Submenu]]></string>
+    <string name="_bx_tasks_menu_item_title_system_view_context_entries"><![CDATA[Tasks]]></string>
+    <string name="_bx_tasks_menu_item_title_view_context_entries"><![CDATA[Tasks]]></string>
+    <string name="_bx_tasks_menu_item_title_system_view_context_time"><![CDATA[My Time]]></string>
+    <string name="_bx_tasks_menu_item_title_view_context_time"><![CDATA[My Time]]></string>
+    <string name="_bx_tasks_menu_item_title_system_view_context_time_administration"><![CDATA[All Time]]></string>
+    <string name="_bx_tasks_menu_item_title_view_context_time_administration"><![CDATA[All Time]]></string>
 
-    <string name="_bx_tasks_grid_filter_item_title_adm_select_one_filter1"><![CDATA[All Statuses]]></string>
-    <string name="_bx_tasks_grid_filter_item_title_adm_select_one_filter2"><![CDATA[Any Audience]]></string>
-    <string name="_bx_tasks_grid_filter_item_title_adm_active"><![CDATA[Active]]></string>
-    <string name="_bx_tasks_grid_filter_item_title_adm_hidden"><![CDATA[Hidden]]></string>
-    <string name="_bx_tasks_grid_column_title_adm_active"><![CDATA[Active]]></string>
-    <string name="_bx_tasks_grid_column_title_adm_title"><![CDATA[Title]]></string>
-    <string name="_bx_tasks_grid_action_title_adm_audit_content"><![CDATA[Audit actions]]></string>
-    <string name="_bx_tasks_grid_column_title_adm_added"><![CDATA[Added]]></string>
-    <string name="_bx_tasks_grid_column_title_adm_author"><![CDATA[Author]]></string>>
-    <string name="_bx_tasks_grid_action_title_adm_add"><![CDATA[Add]]></string>
-    <string name="_bx_tasks_grid_action_title_adm_edit"><![CDATA[Edit]]></string>
-    <string name="_bx_tasks_grid_action_title_adm_delete"><![CDATA[Delete]]></string>
-    <string name="_bx_tasks_grid_action_title_adm_more_actions"><![CDATA[More Actions]]></string>
-    <string name="_bx_tasks_grid_action_err_delete"><![CDATA[Cannot delete the selected task(s).]]></string>
-    <string name="_bx_tasks_grid_txt_account_manager"><![CDATA[Redirect to manage account.]]></string>
+    <!-- Grids -->
+    <string name="_bx_tasks_grid_column_title_tm_author_id"><![CDATA[Profile]]></string>
+    <string name="_bx_tasks_grid_column_title_tm_object_id"><![CDATA[Task]]></string>
+    <string name="_bx_tasks_grid_column_title_tm_text"><![CDATA[Note]]></string>
+    <string name="_bx_tasks_grid_column_title_tm_value"><![CDATA[Time]]></string>
+    <string name="_bx_tasks_grid_column_title_tm_date"><![CDATA[Added]]></string>
+    <string name="_bx_tasks_grid_action_title_tm_add"><![CDATA[Add]]></string>
+    <string name="_bx_tasks_grid_action_title_tm_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_tasks_grid_action_title_tm_delete"><![CDATA[Remove]]></string>
+    <string name="_bx_tasks_grid_action_title_tm_calculate"><![CDATA[Calculate]]></string>
+    <string name="_bx_tasks_grid_filter_item_title_tm_select_one_author_id"><![CDATA[Any Assignee]]></string>
+    <string name="_bx_tasks_grid_filter_item_title_tm_select_one_object_id"><![CDATA[Any Task]]></string>
 
     <string name="_bx_tasks_chart_growth"><![CDATA[Tasks: Growth]]></string>
     <string name="_bx_tasks_chart_growth_speed"><![CDATA[Tasks: Growth Speed]]></string>
@@ -343,6 +379,9 @@
     <string name="_bx_tasks_cat_Weddings"><![CDATA[Weddings]]></string>
     <string name="_bx_tasks_cat_Womens_Fashion"><![CDATA[Women's Fashion]]></string>
 
+    <string name="_bx_tasks_report_time_do_report"><![CDATA[Time]]></string>
+    <string name="_bx_tasks_report_time_do_unreport"><![CDATA[Revoke Time]]></string>
+
     <string name="_bx_tasks_txt_all_entries_in"><![CDATA[<a href="{0}">All Tasks in {1} ({2})</a>]]></string>
     <string name="_bx_tasks_txt_all_entries_by"><![CDATA[{0} tasks]]></string>
     <string name="_bx_tasks_txt_sample_single"><![CDATA[task]]></string>

+ 7 - 2
modules/boonex/tasks/install/sql/disable.sql

@@ -8,12 +8,12 @@ DELETE FROM `sys_options_types` WHERE `id` = @iTypeId;
 
 -- PAGES
 DELETE FROM `sys_objects_page` WHERE `module` = 'bx_tasks';
-DELETE FROM `sys_pages_blocks` WHERE `module` = 'bx_tasks' OR `object` IN('bx_tasks_create_entry', 'bx_tasks_edit_entry', 'bx_tasks_delete_entry', 'bx_tasks_view_entry', 'bx_tasks_view_entry_comments', 'bx_tasks_home', 'bx_tasks_popular', 'bx_tasks_top', 'bx_tasks_updated', 'bx_tasks_author', 'bx_tasks_context', 'bx_tasks_search', 'bx_tasks_manage');
+DELETE FROM `sys_pages_blocks` WHERE `module` = 'bx_tasks' OR `object` IN('bx_tasks_create_entry', 'bx_tasks_edit_entry', 'bx_tasks_delete_entry', 'bx_tasks_view_entry', 'bx_tasks_view_entry_comments', 'bx_tasks_context', 'bx_tasks_context_time', 'bx_tasks_context_time_administration');
 
 -- MENU
 DELETE FROM `sys_objects_menu` WHERE `module` = 'bx_tasks';
 DELETE FROM `sys_menu_sets` WHERE `module` = 'bx_tasks';
-DELETE FROM `sys_menu_items` WHERE `module` = 'bx_tasks' OR `set_name` IN('bx_tasks_create_task_attachments', 'bx_tasks_view', 'bx_tasks_view_actions', 'bx_tasks_view_submenu', 'bx_tasks_my', 'bx_tasks_menu_manage_tools');
+DELETE FROM `sys_menu_items` WHERE `module` = 'bx_tasks' OR `set_name` IN('bx_tasks_create_task_attachments', 'bx_tasks_view', 'bx_tasks_view_actions', 'bx_tasks_view_submenu', 'bx_tasks_view_context_submenu');
 
 -- PRIVACY 
 DELETE FROM `sys_objects_privacy` WHERE `object` = 'bx_tasks_allow_view_to';
@@ -40,6 +40,11 @@ DELETE FROM `sys_statistics` WHERE `name` LIKE 'bx_tasks%';
 -- CHARTS
 DELETE FROM `sys_objects_chart` WHERE `object` LIKE 'bx_tasks%';
 
+-- GRIDS
+DELETE FROM `sys_objects_grid` WHERE `object` LIKE 'bx_tasks_%';
+DELETE FROM `sys_grid_fields` WHERE `object` LIKE 'bx_tasks_%';
+DELETE FROM `sys_grid_actions` WHERE `object` LIKE 'bx_tasks_%';
+
 -- UPLOADERS
 DELETE FROM `sys_objects_uploader` WHERE `object` LIKE 'bx_tasks_%';
 

+ 61 - 4
modules/boonex/tasks/install/sql/enable.sql

@@ -77,13 +77,29 @@ INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `t
 ('bx_tasks_view_entry_comments', 1, 'bx_tasks', '_bx_tasks_page_block_title_entry_comments', '_bx_tasks_page_block_title_entry_comments_link', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:8:\"bx_tasks\";s:6:\"method\";s:15:\"entity_comments\";}', 0, 0, 1);
 
 -- PAGE: entries in context
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_tasks_context', 'tasks-context', '_bx_tasks_page_title_sys_entries_in_context', '_bx_tasks_page_title_entries_in_context', 'bx_tasks', 13, 510, 1, '', '', '', '', 0, 1, 0, 'BxTasksPageAuthor', 'modules/boonex/tasks/classes/BxTasksPageAuthor.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_tasks_context', 2, 'bx_tasks', '_bx_tasks_page_block_title_sys_menu_in_context', '_bx_tasks_page_block_title_menu_in_context', 13, 2147483647, 'service', 'a:3:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:22:"get_block_menu_context";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 0, 1, 1),
+('bx_tasks_context', 3, 'bx_tasks', '_bx_tasks_page_block_title_sys_entries_in_context', '_bx_tasks_page_block_title_entries_in_context', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:8:\"bx_tasks\";s:6:\"method\";s:14:\"browse_context\";}', 0, 0, 1, 1),
+('bx_tasks_context', 4, 'bx_tasks', '_bx_tasks_page_block_title_sys_calendar_in_context', '_bx_tasks_page_block_title_calendar_in_context', 11, 2147483647, 'service', 'a:4:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:8:"calendar";s:12:"ignore_cache";b:1;s:6:"params";a:1:{i:0;a:1:{s:10:"context_id";s:12:"{profile_id}";}}}', 0, 0, 1, 2);
+
+-- PAGE: entries' time in context (own)
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_tasks_context_time', 'tasks-context-time', '_bx_tasks_page_title_sys_entries_time_in_context', '_bx_tasks_page_title_entries_time_in_context', 'bx_tasks', 13, 510, 1, '', '', '', '', 0, 1, 0, 'BxTasksPageAuthor', 'modules/boonex/tasks/classes/BxTasksPageAuthor.php');
+
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_tasks_context_time', 2, 'bx_tasks', '_bx_tasks_page_block_title_sys_menu_in_context', '_bx_tasks_page_block_title_menu_in_context', 13, 2147483647, 'service', 'a:3:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:22:"get_block_menu_context";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 0, 1, 1),
+('bx_tasks_context_time', 3, 'bx_tasks', '_bx_tasks_page_block_title_sys_entries_time_in_context', '_bx_tasks_page_block_title_entries_time_in_context', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:29:"get_block_manage_time_context";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 0, 1, 1);
 
+-- PAGE: entries' time in context (all)
 INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
-('bx_tasks_context', 'tasks-context', '_bx_tasks_page_title_sys_entries_in_context', '_bx_tasks_page_title_entries_in_context', 'bx_tasks', 5, 510, 1, '', '', '', '', 0, 1, 0, 'BxTasksPageAuthor', 'modules/boonex/tasks/classes/BxTasksPageAuthor.php');
+('bx_tasks_context_time_administration', 'tasks-context-time-administration', '_bx_tasks_page_title_sys_entries_time_in_context_administration', '_bx_tasks_page_title_entries_time_in_context_administration', 'bx_tasks', 13, 510, 1, '', '', '', '', 0, 1, 0, 'BxTasksPageAuthor', 'modules/boonex/tasks/classes/BxTasksPageAuthor.php');
 
 INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
-('bx_tasks_context', 1, 'bx_tasks', '_bx_tasks_page_block_title_sys_entries_in_context', '_bx_tasks_page_block_title_entries_in_context', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:8:\"bx_tasks\";s:6:\"method\";s:14:\"browse_context\";}', 0, 0, 1, 1),
-('bx_tasks_context', 1, 'bx_tasks', '_bx_tasks_page_block_title_sys_calendar_in_context', '_bx_tasks_page_block_title_calendar_in_context', 11, 2147483647, 'service', 'a:4:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:8:"calendar";s:12:"ignore_cache";b:1;s:6:"params";a:1:{i:0;a:1:{s:10:"context_id";s:12:"{profile_id}";}}}', 0, 0, 1, 2);
+('bx_tasks_context_time_administration', 2, 'bx_tasks', '_bx_tasks_page_block_title_sys_menu_in_context', '_bx_tasks_page_block_title_menu_in_context', 13, 2147483647, 'service', 'a:3:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:22:"get_block_menu_context";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 0, 1, 1),
+('bx_tasks_context_time_administration', 3, 'bx_tasks', '_bx_tasks_page_block_title_sys_entries_time_in_context_administration', '_bx_tasks_page_block_title_entries_time_in_context_administration', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:35:"get_block_administrate_time_context";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 0, 1, 1);
 
 -- MENU: create task form attachments (link, photo, video, etc)
 
@@ -129,6 +145,7 @@ INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `titl
 ('bx_tasks_view_actions', 'bx_tasks', 'edit-task', '_bx_tasks_menu_item_title_system_edit_entry', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 10),
 ('bx_tasks_view_actions', 'bx_tasks', 'edit-task-state', '_bx_tasks_menu_item_title_system_edit_entry_state', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 12),
 ('bx_tasks_view_actions', 'bx_tasks', 'delete-task', '_bx_tasks_menu_item_title_system_delete_entry', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 20),
+('bx_tasks_view_actions', 'bx_tasks', 'report-time', '_bx_tasks_menu_item_title_system_report_time', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 25),
 ('bx_tasks_view_actions', 'bx_tasks', 'set-completed', '_bx_tasks_menu_item_title_system_set_completed', '_bx_tasks_menu_item_title_set_completed', 'javascript:void(0)', 'javascript:{js_object}.setCompletedByMenu({content_id}, 1, this);', '', 'check', '', '', 0, 2147483647, 'a:3:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:22:"check_allowed_complete";s:6:"params";a:1:{i:0;s:12:"{content_id}";}}', 1, 0, 30),
 ('bx_tasks_view_actions', 'bx_tasks', 'set-uncompleted', '_bx_tasks_menu_item_title_system_set_uncompleted', '_bx_tasks_menu_item_title_set_uncompleted', 'javascript:void(0)', 'javascript:{js_object}.setCompletedByMenu({content_id}, 0, this);', '', 'circle', '', '', 0, 2147483647, 'a:3:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:24:"check_allowed_uncomplete";s:6:"params";a:1:{i:0;s:12:"{content_id}";}}', 1, 0, 35),
 ('bx_tasks_view_actions', 'bx_tasks', 'approve', '_sys_menu_item_title_system_va_approve', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 40),
@@ -157,8 +174,19 @@ INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `titl
 ('bx_tasks_view_submenu', 'bx_tasks', 'view-task', '_bx_tasks_menu_item_title_system_view_entry', '_bx_tasks_menu_item_title_view_entry_submenu_entry', 'page.php?i=view-task&id={content_id}', '', '', '', '', 2147483647, 0, 0, 1),
 ('bx_tasks_view_submenu', 'bx_tasks', 'view-task-comments', '_bx_tasks_menu_item_title_system_view_entry_comments', '_bx_tasks_menu_item_title_view_entry_submenu_comments', 'page.php?i=view-task-comments&id={content_id}', '', '', '', '', 2147483647, 0, 0, 2);
 
--- MENU: add menu item to profiles modules (trigger* menu sets are processed separately upon modules enable/disable)
+-- MENU: context sub-menu
+INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
+('bx_tasks_view_context_submenu', '_bx_tasks_menu_title_view_context_submenu', 'bx_tasks_view_context_submenu', 'bx_tasks', 6, 0, 1, 'BxTasksMenuView', 'modules/boonex/tasks/classes/BxTasksMenuView.php');
+
+INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
+('bx_tasks_view_context_submenu', 'bx_tasks', '_bx_tasks_menu_set_title_view_context_submenu', 0);
 
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `visibility_custom`, `active`, `copyable`, `order`) VALUES 
+('bx_tasks_view_context_submenu', 'bx_tasks', 'tasks-context', '_bx_tasks_menu_item_title_system_view_context_entries', '_bx_tasks_menu_item_title_view_context_entries', 'page.php?i=tasks-context&profile_id={profile_id}', '', '', '', '', 2147483647, '', 1, 0, 1),
+('bx_tasks_view_context_submenu', 'bx_tasks', 'tasks-context-time', '_bx_tasks_menu_item_title_system_view_context_time', '_bx_tasks_menu_item_title_view_context_time', 'page.php?i=tasks-context-time&profile_id={profile_id}', '', '', '', '', 2147483647, '', 1, 0, 2),
+('bx_tasks_view_context_submenu', 'bx_tasks', 'tasks-context-time-administration', '_bx_tasks_menu_item_title_system_view_context_time_administration', '_bx_tasks_menu_item_title_view_context_time_administration', 'page.php?i=tasks-context-time-administration&profile_id={profile_id}', '', '', '', '', 2147483647, 'a:3:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:31:"check_allowed_manage_in_context";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 1, 0, 3);
+
+-- MENU: add menu item to profiles modules (trigger* menu sets are processed separately upon modules enable/disable)
 INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
 ('trigger_group_view_submenu', 'bx_tasks', 'tasks-context', '_bx_tasks_menu_item_title_system_view_entries_in_context', '_bx_tasks_menu_item_title_view_entries_in_context', 'page.php?i=tasks-context&profile_id={profile_id}', '', '', 'tasks col-red3', '', 510, 1, 0, 0);
 
@@ -269,6 +297,35 @@ INSERT INTO `sys_objects_chart` (`object`, `title`, `table`, `field_date_ts`, `f
 ('bx_tasks_growth', '_bx_tasks_chart_growth', 'bx_tasks_tasks', 'added', '', 'status,status_admin', '', 1, @iMaxOrderCharts + 1, 'BxDolChartGrowth', ''),
 ('bx_tasks_growth_speed', '_bx_tasks_chart_growth_speed', 'bx_tasks_tasks', 'added', '', 'status,status_admin', '', 1, @iMaxOrderCharts + 2, 'BxDolChartGrowthSpeed', '');
 
+-- GRIDS: moderation time tools
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `override_class_name`, `override_class_file`) VALUES
+('bx_tasks_time_administration', 'Sql', 'SELECT `ttt`.*, `tt`.`title` FROM `bx_tasks_time_track` AS `ttt` INNER JOIN `bx_tasks_tasks` AS `tt` ON `ttt`.`object_id`=`tt`.`id` WHERE 1 ', 'bx_tasks_time_track', 'id', 'date', '', '', 20, NULL, 'start', '', 'ttt`.`text,tt`.`title,tt`.`text', '', 'like', 'date', '', 192, 'BxTasksGridTimeAdministration', 'modules/boonex/tasks/classes/BxTasksGridTimeAdministration.php'),
+('bx_tasks_time', 'Sql', 'SELECT `ttt`.*, `tt`.`title` FROM `bx_tasks_time_track` AS `ttt` INNER JOIN `bx_tasks_tasks` AS `tt` ON `ttt`.`object_id`=`tt`.`id` WHERE 1 ', 'bx_tasks_time_track', 'id', 'date', '', '', 20, NULL, 'start', '', 'ttt`.`text,tt`.`title,tt`.`text', '', 'like', 'date', '', 2147483647, 'BxTasksGridTime', 'modules/boonex/tasks/classes/BxTasksGridTime.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_tasks_time_administration', 'checkbox', '_sys_select', '2%', 0, 0, '', 1),
+('bx_tasks_time_administration', 'author_id', '_bx_tasks_grid_column_title_tm_author_id', '28%', 0, 0, '', 2),
+('bx_tasks_time_administration', 'object_id', '_bx_tasks_grid_column_title_tm_object_id', '30%', 0, 0, '', 3),
+('bx_tasks_time_administration', 'text', '_bx_tasks_grid_column_title_tm_text', '15%', 0, 16, '', 4),
+('bx_tasks_time_administration', 'value', '_bx_tasks_grid_column_title_tm_value', '10%', 0, 0, '', 5),
+('bx_tasks_time_administration', 'date', '_bx_tasks_grid_column_title_tm_date', '15%', 0, 0, '', 6),
+
+('bx_tasks_time', 'checkbox', '_sys_select', '2%', 0, 0, '', 1),
+('bx_tasks_time', 'object_id', '_bx_tasks_grid_column_title_tm_object_id', '30%', 0, 0, '', 2),
+('bx_tasks_time', 'text', '_bx_tasks_grid_column_title_tm_text', '25%', 0, 32, '', 3),
+('bx_tasks_time', 'value', '_bx_tasks_grid_column_title_tm_value', '10%', 0, 0, '', 4),
+('bx_tasks_time', 'date', '_bx_tasks_grid_column_title_tm_date', '15%', 0, 0, '', 5),
+('bx_tasks_time', 'actions', '', '18%', 0, '', '', 6);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon_only`, `confirm`, `order`) VALUES
+('bx_tasks_time_administration', 'bulk', 'calculate', '_bx_tasks_grid_action_title_tm_calculate', '', 0, 0, 1),
+('bx_tasks_time_administration', 'bulk', 'delete', '_bx_tasks_grid_action_title_tm_delete', '', 0, 1, 2),
+
+('bx_tasks_time', 'independent', 'add', '_bx_tasks_grid_action_title_tm_add', '', 0, 0, 1),
+('bx_tasks_time', 'bulk', 'delete', '_bx_tasks_grid_action_title_tm_delete', '', 0, 1, 1),
+('bx_tasks_time', 'single', 'edit', '_bx_tasks_grid_action_title_tm_edit', 'pencil-alt', 1, 0, 1),
+('bx_tasks_time', 'single', 'delete', '_bx_tasks_grid_action_title_tm_delete', 'remove', 1, 1, 2);
+
 -- UPLOADERS
 
 INSERT INTO `sys_objects_uploader` (`object`, `active`, `override_class_name`, `override_class_file`) VALUES

+ 83 - 16
modules/boonex/tasks/install/sql/install.sql

@@ -9,11 +9,13 @@ CREATE TABLE IF NOT EXISTS `bx_tasks_tasks` (
   `title` varchar(255) NOT NULL,
   `type` int(11) NOT NULL default '0',
   `priority` int(11) NOT NULL default '0',
+  `estimate` int(11) NOT NULL default '0',
   `state` int(11) NOT NULL default '0',
   `cat` int(11) NOT NULL,
   `multicat` text NOT NULL,
   `text` mediumtext NOT NULL,
   `labels` text NOT NULL,
+  `time` int(11) NOT NULL default '0',
   `views` int(11) NOT NULL default '0',
   `rate` float NOT NULL default '0',
   `votes` int(11) NOT NULL default '0',
@@ -292,6 +294,27 @@ CREATE TABLE IF NOT EXISTS `bx_tasks_reports_track` (
   KEY `report` (`object_id`, `author_nip`)
 );
 
+-- TABLE: time
+CREATE TABLE IF NOT EXISTS `bx_tasks_time` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `object_id` int(11) NOT NULL default '0',
+  `count` int(11) NOT NULL default '0',
+  `sum` int(11) NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `object_id` (`object_id`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_tasks_time_track` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `object_id` int(11) NOT NULL default '0',
+  `author_id` int(11) NOT NULL default '0',
+  `author_nip` int(11) unsigned NOT NULL default '0',
+  `value` int(11) NOT NULL default '0',
+  `text` text NOT NULL default '',
+  `date` int(11) NOT NULL default '0',
+  PRIMARY KEY (`id`)
+);
+
 -- TABLE: favorites
 CREATE TABLE `bx_tasks_favorites_track` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
@@ -323,6 +346,18 @@ CREATE TABLE IF NOT EXISTS `bx_tasks_scores_track` (
   KEY `vote` (`object_id`, `author_nip`)
 );
 
+-- TABLE: hours
+CREATE TABLE IF NOT EXISTS `bx_tasks_votes_track` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `object_id` int(11) NOT NULL default '0',
+  `author_id` int(11) NOT NULL default '0',
+  `author_nip` int(11) unsigned NOT NULL default '0',
+  `value` tinyint(4) NOT NULL default '0',
+  `date` int(11) NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  KEY `vote` (`object_id`, `author_nip`)
+);
+
 
 -- STORAGES & TRANSCODERS
 SET @sStorageEngine = (SELECT `value` FROM `sys_options` WHERE `name` = 'sys_storage_default');
@@ -408,7 +443,8 @@ INSERT INTO `sys_form_inputs`(`object`, `module`, `name`, `value`, `values`, `ch
 ('bx_tasks', 'bx_tasks', 'added', '', '', 0, 'datetime', '_bx_tasks_form_entry_input_sys_date_added', '_bx_tasks_form_entry_input_date_added', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
 ('bx_tasks', 'bx_tasks', 'changed', '', '', 0, 'datetime', '_bx_tasks_form_entry_input_sys_date_changed', '_bx_tasks_form_entry_input_date_changed', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
 ('bx_tasks', 'bx_tasks', 'published', '', '', 0, 'datetime', '_bx_tasks_form_entry_input_sys_date_published', '_bx_tasks_form_entry_input_date_published', '_bx_tasks_form_entry_input_date_published_info', 0, 0, 0, '', '', '', '', '', '', 'DateTimeTs', '', 1, 0),
-('bx_tasks', 'bx_tasks', 'initial_members', '', '', 0, 'custom', '_bx_tasks_form_profile_input_sys_initial_members', '_bx_tasks_form_profile_input_initial_members', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 1),
+('bx_tasks', 'bx_tasks', 'initial_members', '', '', 0, 'custom', '_bx_tasks_form_entry_input_sys_initial_members', '_bx_tasks_form_entry_input_initial_members', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 1),
+('bx_tasks', 'bx_tasks', 'estimate', '', '', 0, 'text', '_bx_tasks_form_entry_input_sys_estimate', '_bx_tasks_form_entry_input_estimate', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
 ('bx_tasks', 'bx_tasks', 'due_date', '', '', 0, 'datetime', '_bx_tasks_form_entry_input_sys_date_due_date', '_bx_tasks_form_entry_input_date_due_date', '', 0, 0, 0, '', '', '', '', '', '', 'DateTimeTs', '', 1, 0),
 ('bx_tasks', 'bx_tasks', 'attachments', '', '', 0, 'custom', '_bx_tasks_form_entry_input_sys_attachments', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
 ('bx_tasks', 'bx_tasks', 'labels', '', '', 0, 'custom', '_sys_form_input_sys_labels', '_sys_form_input_labels', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
@@ -422,9 +458,10 @@ INSERT INTO `sys_form_display_inputs`(`display_name`, `input_name`, `visible_for
 ('bx_tasks_entry_add', 'due_date', 192, 1, 4),
 ('bx_tasks_entry_add', 'type', 2147483647, 1, 5),
 ('bx_tasks_entry_add', 'priority', 2147483647, 1, 6),
-('bx_tasks_entry_add', 'controls', 2147483647, 1, 7),
-('bx_tasks_entry_add', 'do_publish', 2147483647, 1, 8),
-('bx_tasks_entry_add', 'do_cancel', 2147483647, 1, 9),
+('bx_tasks_entry_add', 'estimate', 2147483647, 1, 7),
+('bx_tasks_entry_add', 'controls', 2147483647, 1, 8),
+('bx_tasks_entry_add', 'do_publish', 2147483647, 1, 9),
+('bx_tasks_entry_add', 'do_cancel', 2147483647, 1, 10),
 
 ('bx_tasks_entry_delete', 'delete_confirm', 2147483647, 1, 1),
 ('bx_tasks_entry_delete', 'do_submit', 2147483647, 1, 2),
@@ -437,13 +474,14 @@ INSERT INTO `sys_form_display_inputs`(`display_name`, `input_name`, `visible_for
 ('bx_tasks_entry_edit', 'files', 2147483647, 1, 6),
 ('bx_tasks_entry_edit', 'allow_view_to', 2147483647, 1, 7),
 ('bx_tasks_entry_edit', 'cf', 2147483647, 1, 8),
-('bx_tasks_entry_edit', 'initial_members', 192, 1, 8),
+('bx_tasks_entry_edit', 'initial_members', 192, 1, 9),
 ('bx_tasks_entry_edit', 'due_date', 192, 1, 10),
 ('bx_tasks_entry_edit', 'type', 2147483647, 1, 11),
 ('bx_tasks_entry_edit', 'priority', 2147483647, 1, 12),
-('bx_tasks_entry_edit', 'controls_edit', 2147483647, 1, 13),
-('bx_tasks_entry_edit', 'do_submit', 2147483647, 1, 14),
-('bx_tasks_entry_edit', 'do_cancel_edit', 2147483647, 1, 15),
+('bx_tasks_entry_edit', 'estimate', 2147483647, 1, 13),
+('bx_tasks_entry_edit', 'controls_edit', 2147483647, 1, 14),
+('bx_tasks_entry_edit', 'do_submit', 2147483647, 1, 15),
+('bx_tasks_entry_edit', 'do_cancel_edit', 2147483647, 1, 16),
 
 ('bx_tasks_entry_edit_state', 'state', 2147483647, 1, 1),
 ('bx_tasks_entry_edit_state', 'controls_edit_popup', 2147483647, 1, 2),
@@ -452,11 +490,12 @@ INSERT INTO `sys_form_display_inputs`(`display_name`, `input_name`, `visible_for
 
 ('bx_tasks_entry_view', 'type', 2147483647, 1, 1),
 ('bx_tasks_entry_view', 'priority', 2147483647, 1, 2),
-('bx_tasks_entry_view', 'cat', 2147483647, 1, 3),
-('bx_tasks_entry_view', 'added', 2147483647, 1, 4),
-('bx_tasks_entry_view', 'changed', 2147483647, 1, 5),
-('bx_tasks_entry_view', 'due_date', 192, 1, 6),
-('bx_tasks_entry_view', 'state', 2147483647, 1, 7);
+('bx_tasks_entry_view', 'estimate', 2147483647, 1, 3),
+('bx_tasks_entry_view', 'cat', 2147483647, 1, 4),
+('bx_tasks_entry_view', 'added', 2147483647, 1, 5),
+('bx_tasks_entry_view', 'changed', 2147483647, 1, 6),
+('bx_tasks_entry_view', 'due_date', 192, 1, 7),
+('bx_tasks_entry_view', 'state', 2147483647, 1, 8);
 
 
 -- FORMS: entry (tasklist)
@@ -473,7 +512,6 @@ INSERT INTO `sys_form_inputs`(`object`, `module`, `name`, `value`, `values`, `ch
 ('bx_tasks_list', 'bx_tasks', 'do_cancel', '_bx_tasks_form_entry_input_do_cancel', '', 0, 'button', '_bx_tasks_form_entry_input_sys_do_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 1, 0),
 ('bx_tasks_list', 'bx_tasks', 'controls', '', 'do_submit,do_cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0);
 
-
 INSERT INTO `sys_form_display_inputs`(`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES 
 ('bx_tasks_list_entry_add', 'title', 2147483647, 1, 1),
 ('bx_tasks_list_entry_add', 'controls', 2147483647, 1, 2),
@@ -485,6 +523,34 @@ INSERT INTO `sys_form_display_inputs`(`display_name`, `input_name`, `visible_for
 ('bx_tasks_list_entry_edit', 'do_submit', 2147483647, 1, 3),
 ('bx_tasks_list_entry_edit', 'do_cancel', 2147483647, 1, 4);
 
+-- FORMS: time
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_tasks_time', 'bx_tasks', '_bx_tasks_form_time', 'report.php', 'a:3:{s:2:"id";s:0:"";s:4:"name";s:0:"";s:5:"class";s:17:"bx-report-do-form";}', 'submit', '', 'id', '', '', '', 0, 1, '', '');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_tasks_time_add', 'bx_tasks', 'bx_tasks_time', '_bx_tasks_form_display_time_add', 0),
+('bx_tasks_time_edit', 'bx_tasks', 'bx_tasks_time', '_bx_tasks_form_display_time_edit', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_tasks_time', 'bx_tasks', 'sys', '', '', 0, 'hidden', '_bx_tasks_form_time_input_sys_sys', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_tasks_time', 'bx_tasks', 'object_id', '', '', 0, 'hidden', '_bx_tasks_form_time_input_sys_object_id', '_bx_tasks_form_time_input_object_id', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('bx_tasks_time', 'bx_tasks', 'action', '', '', 0, 'hidden', '_bx_tasks_form_time_input_sys_action', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0),
+('bx_tasks_time', 'bx_tasks', 'value', '', '', 0, 'time', '_bx_tasks_form_time_input_sys_value', '_bx_tasks_form_time_input_value', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_tasks_time', 'bx_tasks', 'text', '', '', 0, 'textarea', '_bx_tasks_form_time_input_sys_text', '_bx_tasks_form_time_input_text', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_tasks_time', 'bx_tasks', 'submit', '_bx_tasks_form_time_input_submit', '', 0, 'submit', '_bx_tasks_form_time_input_sys_submit', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_tasks_time_add', 'sys', 2147483647, 1, 1),
+('bx_tasks_time_add', 'object_id', 2147483647, 1, 2),
+('bx_tasks_time_add', 'action', 2147483647, 1, 3),
+('bx_tasks_time_add', 'value', 2147483647, 1, 4),
+('bx_tasks_time_add', 'text', 2147483647, 1, 5),
+('bx_tasks_time_add', 'submit', 2147483647, 1, 6),
+
+('bx_tasks_time_edit', 'value', 2147483647, 1, 1),
+('bx_tasks_time_edit', 'text', 2147483647, 1, 2),
+('bx_tasks_time_edit', 'submit', 2147483647, 1, 3);
+
 
 -- PRE-VALUES
 INSERT INTO `sys_form_pre_lists`(`key`, `title`, `module`, `use_for_sets`) VALUES
@@ -573,8 +639,9 @@ INSERT INTO `sys_objects_score` (`name`, `module`, `table_main`, `table_track`,
 ('bx_tasks', 'bx_tasks', 'bx_tasks_scores', 'bx_tasks_scores_track', '604800', '0', 'bx_tasks_tasks', 'id', 'author', 'score', 'sc_up', 'sc_down', '', '');
 
 -- REPORTS
-INSERT INTO `sys_objects_report` (`name`, `module`, `table_main`, `table_track`, `is_on`, `base_url`, `object_comment`, `trigger_table`, `trigger_field_id`, `trigger_field_author`, `trigger_field_count`, `class_name`, `class_file`) VALUES 
-('bx_tasks', 'bx_tasks', 'bx_tasks_reports', 'bx_tasks_reports_track', '1', 'page.php?i=view-task&id={object_id}', 'bx_tasks_notes', 'bx_tasks_tasks', 'id', 'author', 'reports', '', '');
+INSERT INTO `sys_objects_report` (`name`, `module`, `table_main`, `table_track`, `pruning`, `is_on`, `base_url`, `object_comment`, `trigger_table`, `trigger_field_id`, `trigger_field_author`, `trigger_field_count`, `class_name`, `class_file`) VALUES 
+('bx_tasks', 'bx_tasks', 'bx_tasks_reports', 'bx_tasks_reports_track', '31536000', '1', 'page.php?i=view-task&id={object_id}', 'bx_tasks_notes', 'bx_tasks_tasks', 'id', 'author', 'reports', '', ''),
+('bx_tasks_time', 'bx_tasks', 'bx_tasks_time', 'bx_tasks_time_track', '0', '1', 'page.php?i=view-task&id={object_id}', '', 'bx_tasks_tasks', 'id', 'author', 'time', 'BxTasksTime', 'modules/boonex/tasks/classes/BxTasksTime.php');
 
 -- VIEWS
 INSERT INTO `sys_objects_view` (`name`, `module`, `table_track`, `period`, `is_on`, `trigger_table`, `trigger_field_id`, `trigger_field_author`, `trigger_field_count`, `class_name`, `class_file`) VALUES 

+ 3 - 3
modules/boonex/tasks/install/sql/uninstall.sql

@@ -1,6 +1,6 @@
 
 -- TABLES
-DROP TABLE IF EXISTS `bx_tasks_tasks`, `bx_tasks_lists`, `bx_tasks_assignments`, `bx_tasks_covers`, `bx_tasks_files`, `bx_tasks_photos`, `bx_tasks_photos_resized`, `bx_tasks_videos`, `bx_tasks_videos_resized`, `bx_tasks_cmts`, `bx_tasks_cmts_notes`, `bx_tasks_votes`, `bx_tasks_votes_track`, `bx_tasks_reactions`, `bx_tasks_reactions_track`, `bx_tasks_views_track`, `bx_tasks_meta_keywords`, `bx_tasks_meta_locations`, `bx_tasks_meta_mentions`, `bx_tasks_reports`, `bx_tasks_reports_track`, `bx_tasks_favorites_track`, `bx_tasks_scores`, `bx_tasks_scores_track`;
+DROP TABLE IF EXISTS `bx_tasks_tasks`, `bx_tasks_lists`, `bx_tasks_assignments`, `bx_tasks_covers`, `bx_tasks_files`, `bx_tasks_photos`, `bx_tasks_photos_resized`, `bx_tasks_videos`, `bx_tasks_videos_resized`, `bx_tasks_cmts`, `bx_tasks_cmts_notes`, `bx_tasks_votes`, `bx_tasks_votes_track`, `bx_tasks_reactions`, `bx_tasks_reactions_track`, `bx_tasks_views_track`, `bx_tasks_meta_keywords`, `bx_tasks_meta_locations`, `bx_tasks_meta_mentions`, `bx_tasks_reports`, `bx_tasks_reports_track`, `bx_tasks_time`, `bx_tasks_time_track`, `bx_tasks_favorites_track`, `bx_tasks_scores`, `bx_tasks_scores_track`;
 
 -- STORAGES & TRANSCODERS
 DELETE FROM `sys_objects_storage` WHERE `object` LIKE 'bx_tasks_%';
@@ -19,7 +19,7 @@ DELETE FROM `sys_form_display_inputs` WHERE `display_name` LIKE 'bx_tasks_%';
 -- PRE-VALUES
 DELETE FROM `sys_form_pre_lists` WHERE `module` = 'bx_tasks';
 
-DELETE FROM `sys_form_pre_values` WHERE `Key` IN('bx_tasks_cats');
+DELETE FROM `sys_form_pre_values` WHERE `Key` LIKE 'bx_tasks_%';
 
 -- CATEGORIES
 DELETE FROM `sys_categories` WHERE `module` = 'bx_tasks';
@@ -35,7 +35,7 @@ DELETE FROM `sys_objects_vote` WHERE `Name` IN ('bx_tasks', 'bx_tasks_reactions'
 DELETE FROM `sys_objects_score` WHERE `name` = 'bx_tasks';
 
 -- REPORTS
-DELETE FROM `sys_objects_report` WHERE `name` = 'bx_tasks';
+DELETE FROM `sys_objects_report` WHERE `name` IN ('bx_tasks', 'bx_tasks_time');
 
 -- VIEWS
 DELETE FROM `sys_objects_view` WHERE `name` = 'bx_tasks';

+ 3 - 3
modules/boonex/tasks/template/browse_tasks.html

@@ -41,13 +41,13 @@
                         <label for="checkbox__id__"></label>
                     </div>
 
-                    <a href="__url__" class="bx-tasks-task-name pr-2" title="__title__">__title__</a>
+                    <a href="__url__" class="bx-tasks-task-name pr-2" title="__title__">__title__ </a>
+                    <span class="bx-task-badge pr-2">__badges__</span>
                     <span class="bx-def-font-small bx-def-font-grayed pr-2">__created__</span>
                     <span class="bx-def-font-small bx-def-font-grayed pr-2">__type__</span>
                     <span class="bx-def-font-small col-green1 pr-2">__priority__</span>
                     <span class="bx-task-due-date bx-def-font-small col-red1 pr-2">__due__</span>
-                    <span class="bx-def-font-small bx-def-font-grayed pr-2">__state__</span>
-                    __badges__
+                    <span class="bx-task-state bx-def-font-small bx-def-font-grayed pr-2">__state__</span>
                     <div class="bx-tasks-members">
                         <bx_repeat:members>
                             __info__

+ 46 - 45
modules/boonex/tasks/template/css/main.css

@@ -13,33 +13,19 @@
 	max-width: calc(100% - 220px);
 }
 
-.bx-tasks-members .bx-base-pofile-unit-wo-info {
-    width: 2rem;
-    height: 2rem;
-	display: inline-block;   
-}
-.bx-tasks-members .bx-def-thumb-size{
-	width: 2rem;
-    height: 2rem;
-	 font-size: 1rem;
-}
-.bx-tasks-members div.bx-base-pofile-unit-wo-info {
-    margin-left: -1rem;
-}
-
 .bx-tasks-task-checkbox{
     display: inline-block;
-	height: 24px;
+    height: 24px;
 }
 .bx-tasks-task-checkbox input[type=checkbox] {
-        display:none;
+    display:none;
 }
 .bx-tasks-task-checkbox  input[type=checkbox] + label{
-	height: 24px;
-	width: 24px;
-	display:inline-block;
-	padding: 0;
-	cursor: pointer;
+    height: 24px;
+    width: 24px;
+    display:inline-block;
+    padding: 0;
+    cursor: pointer;
 }
 
 .bx-tasks-task-checkbox  input[type=checkbox] + label:before{
@@ -53,10 +39,10 @@
 }
 
 .bx-tasks-task-checkbox input[type=checkbox]:checked + label{
-	height: 24px;
-	width: 24px;
-	display:inline-block;
-	padding: 0;
+    height: 24px;
+    width: 24px;
+    display:inline-block;
+    padding: 0;
 }
 .bx-tasks-task-checkbox input[type=checkbox]:checked + label:before{
     content: "\f058";
@@ -68,44 +54,59 @@
     line-height: 1;
 }
 .bx-tasks-task{
-	display: flex;
-	justify-content: space-between;
-	align-items: center
+    display: flex;
+    justify-content: space-between;
+    align-items: center
 }
-.bx-tasks-members{
-	flex-grow: 2;
-	text-align: right;
+.bx-task-state {
+    flex-grow: 2;
+    text-align: right;
+}
+.bx-tasks-members {
     display: flex;
     justify-content: flex-end;
+    padding-left: 1rem;
 }
-.bx-tasks-task .bx-badge, .bx-tasks-task .bx-badges-list{
-	display: inline-block;
+.bx-tasks-members .bx-base-pofile-unit-wo-info {
+    display: inline-block;   
+    width: 2rem;
+    height: 2rem;
+    margin-left: -1rem;
+}
+.bx-tasks-members .bx-def-thumb-size{
+    width: 2rem;
+    height: 2rem;
+    font-size: 1rem;
+}
+.bx-tasks-task .bx-badge, 
+.bx-tasks-task .bx-badges-list {
+    display: inline-block;
 }
 .bx-tasks-tasklist .bx-tasks-task-completed{
-	display: none;
+    display: none;
 }
 .bx-tasks-tasklist-filter-all .bx-tasks-task-completed{
-	display: flex;
+    display: flex;
 }
 .bx-tasks-tasklist-filter-completed .bx-tasks-task-uncompleted{
-	display: none;
+    display: none;
 }
 .bx-tasks-tasklist-filter-completed .bx-tasks-task-completed{
-	display: flex;
+    display: flex;
 }
 .bx-tasks-tasklist .bx-def-border-bottom:last-child{
-	border: none;
+    border: none;
 }
 .bx-tasks-task .bx-tasks-task-name{
-	max-width: 50%;
-	white-space: nowrap;
-	overflow: hidden;
-	text-overflow: ellipsis;
-	display: inline-block;
+    max-width: 50%;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    display: inline-block;
 }
 .ui-autocomplete{
-	z-index: 10000 !important;
+    z-index: 10000 !important;
 }
 .bx-media-desktop .bx-tasks-form-cnt{
-	width: 800px;
+    width: 800px;
 }

+ 19 - 0
modules/boonex/tasks/template/popup_time.html

@@ -0,0 +1,19 @@
+<div class="bx-tasks-time-form">
+    <div class="bx-tasks-time-form-cnt">
+        __form__
+        <script language="javascript">
+            $(document).ready(function () {
+                $("#__form_id__").ajaxForm({ 
+                    dataType: "json",
+                    beforeSubmit: function (formData, jqForm, options) {
+                        bx_loading($("#__form_id__"), true);
+                    },
+                    success: function (data) {
+                        $(".bx-popup-applied:visible").dolPopupHide();
+                        glGrids.__object__.processJson(data, '__action__');
+                    }
+                });
+            });
+        </script>
+    </div>
+</div>

+ 3 - 0
modules/boonex/tasks/template/popup_total.html

@@ -0,0 +1,3 @@
+<div class="bx-tasks-time-total">
+    <div class="bx-tasks-time-total-cnt p-4 text-3xl text-bold">__total__</div>
+</div>

+ 17 - 0
modules/boonex/tasks/template/report_by_list.html

@@ -0,0 +1,17 @@
+<div class="__style_prefix__-by-list">
+    <div class="__style_prefix__-by-list-cnt">
+        <div class="__style_prefix__-by-items">
+            <bx_repeat:list>
+                <div class="__style_prefix__-by-item bx-def-padding-sec bx-clearfix">
+                    <div class="__style_prefix__-bi-thumbnail">__user_unit__</div>
+                    <div class="bx-report-bi-info mt-1">
+                        <p class="bx-report-bii-type flex justify-between items-center"><span class="bx-def-font-bold">__value__</span><span>__date__</span></p>
+                        <bx_if:show_text>
+                            <p class="bx-report-bii-text bx-def-font-small bx-def-font-grayed">__text__</p>
+                        </bx_if:show_text>
+                    </div>
+                </div>
+            </bx_repeat:list>
+        </div>
+    </div>
+</div>