Index: language/en_us/administration.php =================================================================== --- language/en_us/administration.php (revision 96) +++ language/en_us/administration.php (working copy) @@ -32,6 +32,8 @@ 'config category desc general' => 'General ProjectPier settings', 'config category name mailing' => 'Mailing', 'config category desc mailing' => 'Use this set of settings to set up how ProjectPier should handle email sending. You can use configuration options provided in your php.ini or set it so it uses any other SMTP server', + 'config category name features' => 'Features', + 'config category desc features' => 'Use this set of settings to enable/disable different features and choose between different methods of displaying project data', // --------------------------------------------------- // Options @@ -62,7 +64,7 @@ 'config option name smtp_username' => 'SMTP username', 'config option name smtp_password' => 'SMTP password', 'config option name smtp_secure_connection' => 'Use secure SMTP connection', - + 'config option name per_project_activity_logs' => 'Per-project activity logs', ); // array ?> \ No newline at end of file Index: application/helpers/application.php =================================================================== --- application/helpers/application.php (revision 96) +++ application/helpers/application.php (working copy) @@ -425,6 +425,14 @@ return tpl_fetch(get_template_path('render_application_logs', 'application')); } // render_application_logs + + + function render_project_application_logs($project, $logs) { + tpl_assign('application_logs_project', $project); + tpl_assign('application_logs_entries', $logs); + return tpl_fetch(get_template_path('render_project_application_logs', 'application')); + } // render_application_logs + /** * Render text that says when action was tacken and by who * Index: application/models/application_logs/ApplicationLog.class.php =================================================================== --- application/models/application_logs/ApplicationLog.class.php (revision 96) +++ application/models/application_logs/ApplicationLog.class.php (working copy) @@ -1,134 +1,138 @@ -getTakenById()); - } // getTakenBy - - /** - * Return taken by display name - * - * @access public - * @param void - * @return string - */ - function getTakenByDisplayName() { - $taken_by = $this->getTakenBy(); - return $taken_by instanceof User ? $taken_by->getDisplayName() : lang('n/a'); - } // getTakenByDisplayName - - /** - * Returns true if this application log is made today - * - * @access public - * @param void - * @return boolean - */ - function isToday() { - $now = DateTimeValueLib::now(); - $created_on = $this->getCreatedOn(); - - // getCreatedOn and similar functions can return NULL - if(!($created_on instanceof DateTimeValue)) return false; - - return $now->getDay() == $created_on->getDay() && - $now->getMonth() == $created_on->getMonth() && - $now->getYear() == $created_on->getYear(); - } // isToday - - /** - * Returnst true if this application log was made yesterday - * - * @param void - * @return boolean - */ - function isYesterday() { - $created_on = $this->getCreatedOn(); - if(!($created_on instanceof DateTimeValue)) return false; - - $day_after = $created_on->advance(24 * 60 * 60, false); - $now = DateTimeValueLib::now(); - - return $now->getDay() == $day_after->getDay() && - $now->getMonth() == $day_after->getMonth() && - $now->getYear() == $day_after->getYear(); - } // isYesterday - - /** - * Return project - * - * @access public - * @param void - * @return Project - */ - function getProject() { - return Projects::findById($this->getProjectId()); - } // getProject - - /** - * Return text message for this entry. If is lang formed as 'log' + action + manager name - * - * 'log add projectmessages' - * - * Object name is passed as a first param so it can be used in a message - * - * @access public - * @param void - * @return string - */ - function getText() { - $code = strtolower('log ' . ($this->getAction()) . ' ' . $this->getRelObjectManager()); - return lang($code, $this->getObjectName()); - } // getText - - /** - * Return object connected with this action - * - * @access public - * @param void - * @return ApplicationDataObject - */ - function getObject() { - return get_object_by_manager_and_id($this->getRelObjectId(), $this->getRelObjectManager()); - } // getObject - - /** - * This function will try load related object and return its YRL. If object is not found '' is retuned - * - * @access public - * @param void - * @return string - */ - function getObjectUrl() { - $object = $this->getObject(); - return $object instanceof ApplicationDataObject ? $object->getObjectUrl() : null; - } // getObjectMessage - - /** - * Return object type name - * - * @param void - * @return string - */ - function getObjectTypeName() { - $object = $this->getObject(); - return $object instanceof ApplicationDataObject ? $object->getObjectTypeName() : null; - } // getObjectTypeName - - } // ApplicationLog - +getTakenById()); + } // getTakenBy + + /** + * Return taken by display name + * + * @access public + * @param void + * @return string + */ + function getTakenByDisplayName() { + $taken_by = $this->getTakenBy(); + return $taken_by instanceof User ? $taken_by->getDisplayName() : lang('n/a'); + } // getTakenByDisplayName + + /** + * Returns true if this application log is made today + * + * @access public + * @param void + * @return boolean + */ + function isToday() { + $now = DateTimeValueLib::now(); + $created_on = $this->getCreatedOn(); + + // getCreatedOn and similar functions can return NULL + if(!($created_on instanceof DateTimeValue)) return false; + + return $now->getDay() == $created_on->getDay() && + $now->getMonth() == $created_on->getMonth() && + $now->getYear() == $created_on->getYear(); + } // isToday + + /** + * Returns true if this application log was made yesterday + * + * @param void + * @return boolean + */ + function isYesterday() { + $created_on = $this->getCreatedOn(); + if(!($created_on instanceof DateTimeValue)) return false; + + $day_after = $created_on->advance(24 * 60 * 60, false); + $now = DateTimeValueLib::now(); + + return $now->getDay() == $day_after->getDay() && + $now->getMonth() == $day_after->getMonth() && + $now->getYear() == $day_after->getYear(); + } // isYesterday + + /** + * Return project + * + * @access public + * @param void + * @return Project + */ + function getProject() { + return Projects::findById($this->getProjectId()); + } // getProject + + /** + * Return text message for this entry. If is lang formed as 'log' + action + manager name + * + * 'log add projectmessages' + * + * @access public + * @param void + * @return string + */ + function getText() { + $code = strtolower('log ' . ($this->getAction()) . ' ' . $this->getRelObjectManager()); + $relObject = $this->getObject(); + if ($relObject instanceof Comment) + { + $ownerObject = $relObject->getObject(); + return $ownerObject instanceof ProjectDataObject ? "Re: ".$ownerObject->getObjectName()." - ".substr_utf($relObject->getText(), 0, 50) . '...' : "Re: ".$relObject->getObjectTypeName().""; + } + return lang($code, $this->getObjectName()); + } // getText + + /** + * Return object connected with this action + * + * @access public + * @param void + * @return ApplicationDataObject + */ + function getObject() { + return get_object_by_manager_and_id($this->getRelObjectId(), $this->getRelObjectManager()); + } // getObject + + /** + * This function will try load related object and return its YRL. If object is not found '' is retuned + * + * @access public + * @param void + * @return string + */ + function getObjectUrl() { + $object = $this->getObject(); + return $object instanceof ApplicationDataObject ? $object->getObjectUrl() : null; + } // getObjectMessage + + /** + * Return object type name + * + * @param void + * @return string + */ + function getObjectTypeName() { + $object = $this->getObject(); + return $object instanceof ApplicationDataObject ? $object->getObjectTypeName() : null; + } // getObjectTypeName + + } // ApplicationLog + ?> \ No newline at end of file Index: application/models/application_logs/ApplicationLogs.class.php =================================================================== --- application/models/application_logs/ApplicationLogs.class.php (revision 96) +++ application/models/application_logs/ApplicationLogs.class.php (working copy) @@ -1,207 +1,207 @@ -manager(); - if(!($manager instanceof DataManager)) { - throw new Error('Invalid object manager'); - } // if - - $log = new ApplicationLog(); - - if($project instanceof Project) { - $log->setProjectId($project->getId()); - } // if - $log->setTakenById(logged_user()->getId()); - $log->setRelObjectId($object->getObjectId()); - $log->setObjectName($object->getObjectName()); - $log->setRelObjectManager(get_class($manager)); - $log->setAction($action); - $log->setIsPrivate($is_private); - $log->setIsSilent($is_silent); - - if($save) { - $log->save(); - } // if - - // Update is private for this object - if($object instanceof ProjectDataObject) { - ApplicationLogs::setIsPrivateForObject($object); - } // if - - return $log; - } // createLog - - /** - * Update is_private flag value for all previous related log entries related with specific object - * - * This method is called whenever we need to add new log entry. It will keep old log entries related to that specific - * object with current is_private flag value by updating all of the log entries to new value. - * - * @param ProjectDataObject $object - * @return boolean - */ - static function setIsPrivateForObject(ProjectDataObject $object) { - return DB::execute('UPDATE ' . ApplicationLogs::instance()->getTableName(true) . ' SET `is_private` = ? WHERE `rel_object_id` = ? AND `rel_object_manager` = ?', - $object->isPrivate(), - $object->getObjectId(), - get_class($object->manager() - )); // execute - } // setIsPrivateForObject - - /** - * Mass set is_private for a given type. If $ids is present limit update only to object with given ID-s - * - * @param boolean $is_private - * @param string $type - * @parma array $ids - * @return boolean - */ - static function setIsPrivateForType($is_private, $type, $ids = null) { - $limit_ids = null; - if(is_array($ids)) { - $limit_ids = array(); - foreach($ids as $id) { - $limit_ids[] = DB::escape($id); - } // if - - $limit_ids = count($limit_ids) > 0 ? implode(',', $limit_ids) : null; - } // if - - $sql = DB::prepareString('UPDATE ' . ApplicationLogs::instance()->getTableName(true) . ' SET `is_private` = ? WHERE `rel_object_manager` = ?', array($is_private, $type)); - if($limit_ids !== null) { - $sql .= " AND `rel_object_id` IN ($limit_ids)"; - } // if - - return DB::execute($sql); - } // setIsPrivateForType - - /** - * Return entries related to specific project - * - * If $include_private is set to true private entries will be included in result. If $include_silent is set to true - * logs marked as silent will also be included. $limit and $offset are there to control the range of the result, - * usually we don't want to pull the entire log but just the few most recent entries. If NULL they will be ignored - * - * @param Project $project - * @param boolean $include_private - * @param boolean $include_silent - * @param integer $limit - * @param integer $offset - * @return array - */ - static function getProjectLogs(Project $project, $include_private = false, $include_silent = false, $limit = null, $offset = null) { - $private_filter = $include_private ? 1 : 0; - $silent_filter = $include_silent ? 1 : 0; - - return self::findAll(array( - 'conditions' => array('`is_private` <= ? AND `is_silent` <= ? AND `project_id` = (?)', $private_filter, $silent_filter, $project->getId()), - 'order' => '`created_on` DESC', - 'limit' => $limit, - 'offset' => $offset, - )); // findAll - } // getProjectLogs - - /** - * Return overall (for dashboard or RSS) - * - * This function will return array of application logs that match the function arguments. Entries can be filtered by - * type (prvivate, silent), projects (if $project_ids is array, if NULL project ID is ignored). Result set can be - * also limited using $limit and $offset params - * - * @param boolean $include_private - * @param boolean $include_silent - * @param mixed $project_ids - * @param integer $limit - * @param integer $offset - * @return array - */ - static function getOverallLogs($include_private = false, $include_silent = false, $project_ids = null, $limit = null, $offset = null) { - $private_filter = $include_private ? 1 : 0; - $silent_filter = $include_silent ? 1 : 0; - - if(is_array($project_ids)) { - $conditions = array('`is_private` <= ? AND `is_silent` <= ? AND `project_id` IN (?)', $private_filter, $silent_filter, $project_ids); - } else { - $conditions = array('`is_private` <= ? AND `is_silent` <= ?', $private_filter, $silent_filter); - } // if - - return self::findAll(array( - 'conditions' => $conditions, - 'order' => '`created_on` DESC', - 'limit' => $limit, - 'offset' => $offset, - )); // findAll - } // getOverallLogs - - /** - * Clear all logs related with specific project - * - * @param Project $project - * @return boolean - */ - static function clearByProject(Project $project) { - return self::delete(array('`project_id` = ?', $project->getId())); - } // clearByProject - - /** - * Check if specific action is valid - * - * @param string $action - * @return boolean - */ - static function isValidAction($action) { - static $valid_actions = null; - - if(!is_array($valid_actions)) { - $valid_actions = array( - self::ACTION_ADD, - self::ACTION_EDIT, - self::ACTION_DELETE, - self::ACTION_CLOSE, - self::ACTION_OPEN - ); // array - } // if - - return in_array($action, $valid_actions); - } // isValidAction - - } // ApplicationLogs - +manager(); + if(!($manager instanceof DataManager)) { + throw new Error('Invalid object manager'); + } // if + + $log = new ApplicationLog(); + + if($project instanceof Project) { + $log->setProjectId($project->getId()); + } // if + $log->setTakenById(logged_user()->getId()); + $log->setRelObjectId($object->getObjectId()); + $log->setObjectName($object->getObjectName()); + $log->setRelObjectManager(get_class($manager)); + $log->setAction($action); + $log->setIsPrivate($is_private); + $log->setIsSilent($is_silent); + + if($save) { + $log->save(); + } // if + + // Update is private for this object + if($object instanceof ProjectDataObject) { + ApplicationLogs::setIsPrivateForObject($object); + } // if + + return $log; + } // createLog + + /** + * Update is_private flag value for all previous related log entries related with specific object + * + * This method is called whenever we need to add new log entry. It will keep old log entries related to that specific + * object with current is_private flag value by updating all of the log entries to new value. + * + * @param ProjectDataObject $object + * @return boolean + */ + static function setIsPrivateForObject(ProjectDataObject $object) { + return DB::execute('UPDATE ' . ApplicationLogs::instance()->getTableName(true) . ' SET `is_private` = ? WHERE `rel_object_id` = ? AND `rel_object_manager` = ?', + $object->isPrivate(), + $object->getObjectId(), + get_class($object->manager() + )); // execute + } // setIsPrivateForObject + + /** + * Mass set is_private for a given type. If $ids is present limit update only to object with given ID-s + * + * @param boolean $is_private + * @param string $type + * @parma array $ids + * @return boolean + */ + static function setIsPrivateForType($is_private, $type, $ids = null) { + $limit_ids = null; + if(is_array($ids)) { + $limit_ids = array(); + foreach($ids as $id) { + $limit_ids[] = DB::escape($id); + } // if + + $limit_ids = count($limit_ids) > 0 ? implode(',', $limit_ids) : null; + } // if + + $sql = DB::prepareString('UPDATE ' . ApplicationLogs::instance()->getTableName(true) . ' SET `is_private` = ? WHERE `rel_object_manager` = ?', array($is_private, $type)); + if($limit_ids !== null) { + $sql .= " AND `rel_object_id` IN ($limit_ids)"; + } // if + + return DB::execute($sql); + } // setIsPrivateForType + + /** + * Return entries related to specific project + * + * If $include_private is set to true private entries will be included in result. If $include_silent is set to true + * logs marked as silent will also be included. $limit and $offset are there to control the range of the result, + * usually we don't want to pull the entire log but just the few most recent entries. If NULL they will be ignored + * + * @param Project $project + * @param boolean $include_private + * @param boolean $include_silent + * @param integer $limit + * @param integer $offset + * @return array + */ + static function getProjectLogs(Project $project, $include_private = false, $include_silent = false, $limit = null, $offset = null) { + $private_filter = $include_private ? 1 : 0; + $silent_filter = $include_silent ? 1 : 0; + + return self::findAll(array( + 'conditions' => array('`is_private` <= ? AND `is_silent` <= ? AND `project_id` = (?)', $private_filter, $silent_filter, $project->getId()), + 'order' => '`created_on` DESC', + 'limit' => $limit, + 'offset' => $offset, + )); // findAll + } // getProjectLogs + + /** + * Return overall (for dashboard or RSS) + * + * This function will return array of application logs that match the function arguments. Entries can be filtered by + * type (prvivate, silent), projects (if $project_ids is array, if NULL project ID is ignored). Result set can be + * also limited using $limit and $offset params + * + * @param boolean $include_private + * @param boolean $include_silent + * @param mixed $project_ids + * @param integer $limit + * @param integer $offset + * @return array + */ + static function getOverallLogs($include_private = false, $include_silent = false, $project_ids = null, $limit = null, $offset = null) { + $private_filter = $include_private ? 1 : 0; + $silent_filter = $include_silent ? 1 : 0; + + if(is_array($project_ids)) { + $conditions = array('`is_private` <= ? AND `is_silent` <= ? AND `project_id` IN (?)', $private_filter, $silent_filter, $project_ids); + } else { + $conditions = array('`is_private` <= ? AND `is_silent` <= ?', $private_filter, $silent_filter); + } // if + + return self::findAll(array( + 'conditions' => $conditions, + 'order' => '`created_on` DESC', + 'limit' => $limit, + 'offset' => $offset, + )); // findAll + } // getOverallLogs + + /** + * Clear all logs related with specific project + * + * @param Project $project + * @return boolean + */ + static function clearByProject(Project $project) { + return self::delete(array('`project_id` = ?', $project->getId())); + } // clearByProject + + /** + * Check if specific action is valid + * + * @param string $action + * @return boolean + */ + static function isValidAction($action) { + static $valid_actions = null; + + if(!is_array($valid_actions)) { + $valid_actions = array( + self::ACTION_ADD, + self::ACTION_EDIT, + self::ACTION_DELETE, + self::ACTION_CLOSE, + self::ACTION_OPEN + ); // array + } // if + + return in_array($action, $valid_actions); + } // isValidAction + + } // ApplicationLogs + ?> \ No newline at end of file Index: application/controllers/DashboardController.class.php =================================================================== --- application/controllers/DashboardController.class.php (revision 96) +++ application/controllers/DashboardController.class.php (working copy) @@ -34,10 +34,17 @@ $include_silent = $logged_user->isAdministrator(); $project_ids = array(); + $projects_activity_log = array(); foreach($active_projects as $active_project) { $project_ids[] = $active_project->getId(); + $temp_project_logs = ApplicationLogs::getProjectLogs($active_project,$include_private, $include_silent,config_option('dashboard_project_logs_count',7)); + if (isset($temp_project_logs) && is_array($temp_project_logs) && count($temp_project_logs)) + { + $projects_activity_log[$temp_project_logs[0]->getCreatedOn()->getTimestamp()] = $temp_project_logs; + } + krsort($projects_activity_log); } // if - + $activity_log = ApplicationLogs::getOverallLogs($include_private, $include_silent, $project_ids, config_option('dashboard_logs_count', 15)); } // if @@ -45,6 +52,7 @@ tpl_assign('late_milestones', $logged_user->getLateMilestones()); tpl_assign('active_projects', $active_projects); tpl_assign('activity_log', $activity_log); + tpl_assign('projects_activity_log', $projects_activity_log); // Sidebar tpl_assign('online_users', Users::getWhoIsOnline()); Index: application/views/dashboard/index.php =================================================================== --- application/views/dashboard/index.php (revision 96) +++ application/views/dashboard/index.php (working copy) @@ -85,8 +85,24 @@ - + +getProject(); + ?> + + + + true)) ?> - \ No newline at end of file + \ No newline at end of file Index: application/views/application/render_project_application_logs.php =================================================================== --- application/views/application/render_project_application_logs.php (revision 0) +++ application/views/application/render_project_application_logs.php (revision 0) @@ -0,0 +1,37 @@ + + + + + + + getName() ?> + + + + + +isToday()) { ?> + +isYesterday()) { ?> + + + + + + + + + +
+getObjectUrl()) { ?> + getName(); ?>
<?= $application_log_entry->getObjectTypeName() ?> +getObjectUrl()) { ?> +getText() ?> + + getText() ?> + + +
+ Index: public/install/installation/templates/sql/mysql_initial_data.php =================================================================== --- public/install/installation/templates/sql/mysql_initial_data.php (revision 96) +++ public/install/installation/templates/sql/mysql_initial_data.php (working copy) @@ -4,6 +4,7 @@ INSERT INTO `config_categories` (`name`, `is_system`, `category_order`) VALUES ('system', 1, 0); INSERT INTO `config_categories` (`name`, `is_system`, `category_order`) VALUES ('general', 0, 1); INSERT INTO `config_categories` (`name`, `is_system`, `category_order`) VALUES ('mailing', 0, 2); +INSERT INTO `config_categories` (`name`, `is_system`, `category_order`) VALUES ('features', 0, 3); INSERT INTO `config_options` (`category_name`, `name`, `value`, `config_handler_class`, `is_system`, `option_order`, `dev_comment`) VALUES ('system', 'project_logs_per_page', '10', 'IntegerConfigHandler', 1, 0, NULL); INSERT INTO `config_options` (`category_name`, `name`, `value`, `config_handler_class`, `is_system`, `option_order`, `dev_comment`) VALUES ('system', 'messages_per_page', '5', 'IntegerConfigHandler', 1, 0, NULL); @@ -28,6 +29,7 @@ INSERT INTO `config_options` (`category_name`, `name`, `value`, `config_handler_class`, `is_system`, `option_order`, `dev_comment`) VALUES ('mailing', 'smtp_username', '', 'StringConfigHandler', 0, 0, NULL); INSERT INTO `config_options` (`category_name`, `name`, `value`, `config_handler_class`, `is_system`, `option_order`, `dev_comment`) VALUES ('mailing', 'smtp_password', '', 'PasswordConfigHandler', 0, 0, NULL); INSERT INTO `config_options` (`category_name`, `name`, `value`, `config_handler_class`, `is_system`, `option_order`, `dev_comment`) VALUES ('mailing', 'smtp_secure_connection', 'no', 'SecureSmtpConnectionConfigHandler', 0, 0, 'Values: no, ssl, tls'); +INSERT INTO `config_options` (`category_name`, `name`, `value`, `config_handler_class`, `is_system`, `option_order`, `dev_comment`) VALUES ('features', 'per_project_activity_logs', '0', 'BoolConfigHandler', 0, 0, 'Show recent activity logs per project on the owner company dashboard (like BaseCamp) rather than all mashed together'); INSERT INTO `file_types` (`extension`, `icon`, `is_searchable`, `is_image`) VALUES ('zip', 'archive.png', 0, 0); INSERT INTO `file_types` (`extension`, `icon`, `is_searchable`, `is_image`) VALUES ('rar', 'archive.png', 0, 0);