diff -urN projectpier-0.8.0.3/application/controllers/CommentController.class.php projectpier-0.8.0.3-trackerpatch/application/controllers/CommentController.class.php --- projectpier-0.8.0.3/application/controllers/CommentController.class.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/application/controllers/CommentController.class.php Wed Aug 6 22:01:59 2008 @@ -78,8 +78,8 @@ ApplicationLogs::createLog($comment, active_project(), ApplicationLogs::ACTION_ADD); - // Subscribe user to message (if $object is message) - if ($object instanceof ProjectMessage) { + // Subscribe user to object (if $object is subscribible) + if($object->isSubscribible()) { if (!$object->isSubscriber(logged_user())) { $object->subscribeUser(logged_user()); } // if diff -urN projectpier-0.8.0.3/application/controllers/DashboardController.class.php projectpier-0.8.0.3-trackerpatch/application/controllers/DashboardController.class.php --- projectpier-0.8.0.3/application/controllers/DashboardController.class.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/application/controllers/DashboardController.class.php Wed Aug 6 22:00:17 2008 @@ -75,6 +75,17 @@ tpl_assign('active_projects', logged_user()->getActiveProjects()); $this->setSidebar(get_template_path('my_tasks_sidebar', 'dashboard')); } // my_tasks + + /** + * Return all tickets assigned to this user + * + * @param void + * @return array + */ + function my_tickets() { + tpl_assign('active_projects', logged_user()->getActiveProjects()); + $this->setSidebar(get_template_path('index_sidebar', 'trac')); + } // my_trac } // DashboardController diff -urN projectpier-0.8.0.3/application/controllers/FilesController.class.php projectpier-0.8.0.3-trackerpatch/application/controllers/FilesController.class.php --- projectpier-0.8.0.3/application/controllers/FilesController.class.php Sun Feb 17 15:12:58 2008 +++ projectpier-0.8.0.3-trackerpatch/application/controllers/FilesController.class.php Wed Aug 6 22:00:17 2008 @@ -760,6 +760,7 @@ } // foreach DB::commit(); + $object->onAttachFiles($attach_files); flash_success(lang('success attach files', $counter)); $this->redirectToUrl($object->getObjectUrl()); diff -urN projectpier-0.8.0.3/application/controllers/MessageController.class.php projectpier-0.8.0.3-trackerpatch/application/controllers/MessageController.class.php --- projectpier-0.8.0.3/application/controllers/MessageController.class.php Fri Feb 15 12:51:56 2008 +++ projectpier-0.8.0.3-trackerpatch/application/controllers/MessageController.class.php Wed Aug 6 22:03:39 2008 @@ -385,9 +385,9 @@ } // if if ($message->subscribeUser(logged_user())) { - flash_success('success subscribe to message'); + flash_success(lang('success subscribe to message')); } else { - flash_error('error subscribe to message'); + flash_error(lang('error subscribe to message')); } // if $this->redirectToUrl($message->getViewUrl()); } // subscribe @@ -411,9 +411,9 @@ } // if if ($message->unsubscribeUser(logged_user())) { - flash_success('success unsubscribe to message'); + flash_success(lang('success unsubscribe to message')); } else { - flash_error('error unsubscribe to message'); + flash_error(lang('error unsubscribe to message')); } // if $this->redirectToUrl($message->getViewUrl()); } // unsubscribe diff -urN projectpier-0.8.0.3/application/controllers/TracController.class.php projectpier-0.8.0.3-trackerpatch/application/controllers/TracController.class.php --- projectpier-0.8.0.3/application/controllers/TracController.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/controllers/TracController.class.php Wed Aug 6 22:00:17 2008 @@ -0,0 +1,686 @@ +getId()); + + list($categories, $pagination) = Categories::paginate( + array( + 'conditions' => $conditions, + 'order' => '`name`' + ), + config_option('categories_per_page', 25), + $page + ); // paginate + + tpl_assign('categories', $categories); + tpl_assign('categories_pagination', $pagination); + + $this->setSidebar(get_template_path('trac_sidebar', 'trac')); + } // categories + + /** + * Return project tickets + * + * @access public + * @param void + * @return array + */ + function index() { + $page = (integer) array_var($_GET, 'page', 1); + if($page < 0) $page = 1; + + $closed = (boolean) array_var($_GET, 'closed', false); + $conditions = DB::prepareString('`closed_on` '.($closed ? '>' : '=').' ? and `project_id` = ?', array(EMPTY_DATETIME, active_project()->getId())); + if(!logged_user()->isMemberOfOwnerCompany()) { + $conditions .= DB::prepareString(' AND `is_private` = ?', array(0)); + } // if + + if ($closed) { + $order = '`closed_on` DESC'; + } else { + $order = '`created_on` DESC'; + } // if + + list($tickets, $pagination) = ProjectTickets::paginate( + array( + 'conditions' => $conditions, + 'order' => $order + ), + config_option('tickets_per_page', 25), + $page + ); // paginate + + tpl_assign('closed', $closed); + tpl_assign('tickets', $tickets); + tpl_assign('tickets_pagination', $pagination); + + $this->setSidebar(get_template_path('index_sidebar', 'trac')); + } // index + + /** + * Add ticket + * + * @access public + * @param void + * @return null + */ + function add() { + $this->addHelper('ticket'); + + if(!ProjectTicket::canAdd(logged_user(), active_project())) { + flash_error(lang('no access permissions')); + $this->redirectToReferer(get_url('trac')); + } // if + + $ticket = new ProjectTicket(); + $ticket->setProjectId(active_project()->getId()); + $ticket_data = array_var($_POST, 'ticket'); + + tpl_assign('ticket', $ticket); + tpl_assign('ticket_data', $ticket_data); + + if(is_array(array_var($_POST, 'ticket'))) { + try { + $uploaded_files = ProjectFiles::handleHelperUploads(active_project()); + } catch(Exception $e) { + $uploaded_files = null; + } // try + + try { + $ticket->setFromAttributes($ticket_data); + + $assigned_to = explode(':', array_var($ticket_data, 'assigned_to', '')); + $ticket->setAssignedToCompanyId(array_var($assigned_to, 0, 0)); + $ticket->setAssignedToUserId(array_var($assigned_to, 1, 0)); + + // Options are reserved only for members of owner company + if(!logged_user()->isMemberOfOwnerCompany()) { + $ticket->setIsPrivate(false); + } // if + + DB::beginWork(); + $ticket->save(); + + if(is_array($uploaded_files)) { + foreach($uploaded_files as $uploaded_file) { + $ticket->attachFile($uploaded_file); + $uploaded_file->setIsPrivate($ticket->isPrivate()); + $uploaded_file->setIsVisible(true); + $uploaded_file->setExpirationTime(EMPTY_DATETIME); + $uploaded_file->save(); + } // if + } // if + + ApplicationLogs::createLog($ticket, active_project(), ApplicationLogs::ACTION_ADD); + DB::commit(); + + // Try to send notifications but don't break submission in case of an error + try { + if ($ticket->getAssignedToUserId()) { + $ticket_data['notify_user_' . $ticket->getAssignedToUserId()] = 'checked'; + } + + $notify_people = array(); + $project_companies = active_project()->getCompanies(); + foreach($project_companies as $project_company) { + $company_users = $project_company->getUsersOnProject(active_project()); + if(is_array($company_users)) { + foreach($company_users as $company_user) { + if((array_var($ticket_data, 'notify_company_' . $project_company->getId()) == 'checked') || (array_var($ticket_data, 'notify_user_' . $company_user->getId()))) { + $ticket->subscribeUser($company_user); // subscribe + $notify_people[] = $company_user; + } // if + } // if + } // if + } // if + + Notifier::ticket($ticket, $notify_people, 'new_ticket', $ticket->getCreatedBy()); + } catch(Exception $e) { + + } // try + + flash_success(lang('success add ticket', $ticket->getSummary())); + $this->redirectTo('trac'); + + // Error... + } catch(Exception $e) { + DB::rollback(); + + if(is_array($uploaded_files)) { + foreach($uploaded_files as $uploaded_file) { + $uploaded_file->delete(); + } // foreach + } // if + + $ticket->setNew(true); + tpl_assign('error', $e); + } // try + + } // if + } // add + + /** + * Edit specific ticket + * + * @access public + * @param void + * @return null + */ + function edit() { + $this->addHelper('textile'); + $this->addHelper('ticket'); + + $ticket = ProjectTickets::findById(get_id()); + if(!($ticket instanceof ProjectTicket)) { + flash_error(lang('ticket dnx')); + $this->redirectTo('trac'); + } // if + + if(!$ticket->canView(logged_user())) { + flash_error(lang('no access permissions')); + $this->redirectToReferer(get_url('trac')); + } // if + + $ticket_data = array_var($_POST, 'ticket'); + if(!is_array($ticket_data)) { + $ticket_data = array( + 'is_private' => $ticket->isPrivate(), + 'summary' => $ticket->getSummary(), + 'priority' => $ticket->getPriority(), + 'type' => $ticket->getType(), + 'category_id' => $ticket->getCategoryId(), + 'assigned_to' => $ticket->getAssignedToCompanyId() . ':' . $ticket->getAssignedToUserId() + ); // array + } // if + + tpl_assign('ticket', $ticket); + tpl_assign('ticket_data', $ticket_data); + tpl_assign('subscribers', $ticket->getSubscribers()); + tpl_assign('changes', $ticket->getChanges()); + + $this->setSidebar(get_template_path('edit_sidebar', 'trac')); + + if(is_array(array_var($_POST, 'ticket'))) { + if(!$ticket->canEdit(logged_user())) { + flash_error(lang('no access permissions')); + $this->redirectTo('trac'); + } else { + $old_fields = array( + 'summary' => $ticket->getSummary(), + 'priority' => $ticket->getPriority(), + 'type' => $ticket->getType(), + 'category' => $ticket->getCategory(), + 'assigned to' => $ticket->getAssignedTo() + ); + $old_private = $ticket->isPrivate(); + + try { + $ticket->setFromAttributes($ticket_data); + $ticket->setUpdated('settings'); + + // Options are reserved only for members of owner company + if(!logged_user()->isMemberOfOwnerCompany()) { + $ticket->setIsPrivate($old_private); + } // if + + $old_assigned_user_id = $ticket->getAssignedToUserId(); + $assigned_to = explode(':', array_var($ticket_data, 'assigned_to', '')); + $ticket->setAssignedToCompanyId(array_var($assigned_to, 0, 0)); + $ticket->setAssignedToUserId(array_var($assigned_to, 1, 0)); + + DB::beginWork(); + $ticket->save(); + + ApplicationLogs::createLog($ticket, $ticket->getProject(), ApplicationLogs::ACTION_EDIT); + DB::commit(); + + $user = $ticket->getAssignedToUser(); + if ($user instanceof User && $user->getId() != $old_assigned_user_id) { + if(!$ticket->isSubscriber($user)) { + $ticket->subscribeUser($user); + } // if + } // if + + $new_fields = array( + 'summary' => $ticket->getSummary(), + 'priority' => $ticket->getPriority(), + 'type' => $ticket->getType(), + 'category' => $ticket->getCategory(), + 'assigned to' => $ticket->getAssignedTo() + ); + + foreach ($old_fields as $type => $old_field) { + $new_field = $new_fields[$type]; + if ($old_field === $new_field) { + continue; + } + $from_data = ($old_field instanceof ApplicationDataObject) ? $old_field->getObjectName() : $old_field; + $to_data = ($new_field instanceof ApplicationDataObject) ? $new_field->getObjectName() : $new_field; + + $change = new TicketChange(); + $change->setTicketId($ticket->getId()); + $change->setType($type); + $change->setFromData($from_data); + $change->setToData($to_data); + $change->save(); + } // foreach + + try { + Notifier::ticket($ticket, $ticket->getSubscribers(), 'edit_ticket', $ticket->getUpdatedBy()); + } catch(Exception $e) { + // nothing here, just suppress error... + } // try + + flash_success(lang('success edit ticket', $ticket->getSummary())); + $this->redirectToUrl($ticket->getViewUrl()); + + } catch(Exception $e) { + DB::rollback(); + tpl_assign('error', $e); + } // try + } // if + } // if + } // edit + + /** + * Update message options. This is execute only function and if we don't have + * options in post it will redirect back to the message + * + * @param void + * @return null + */ + function update_options() { + $ticket = ProjectTickets::findById(get_id()); + if(!($ticket instanceof ProjectTicket)) { + flash_error(lang('ticket dnx')); + $this->redirectTo('trac'); + } // if + + if(!$ticket->canUpdateOptions(logged_user())) { + flash_error(lang('no access permissions')); + $this->redirectToReferer(get_url('trac')); + } // if + + $ticket_data = array_var($_POST, 'ticket'); + if(is_array(array_var($_POST, 'ticket'))) { + try { + $old_private = $ticket->isPrivate(); + $ticket->setIsPrivate((boolean) array_var($ticket_data, 'is_private', $ticket->isPrivate())); + + DB::beginWork(); + $ticket->save(); + ApplicationLogs::createLog($ticket, $ticket->getProject(), ApplicationLogs::ACTION_EDIT); + DB::commit(); + + if ($old_private != $ticket->isPrivate()) { + $change = new TicketChange(); + $change->setTicketId($ticket->getId()); + $change->setType('private'); + $change->setFromData($old_private ? 'yes' : 'no'); + $change->setToData($ticket->isPrivate() ? 'yes' : 'no'); + $change->save(); + } + + flash_success(lang('success edit ticket', $ticket->getSummary())); + } catch(Exception $e) { + flash_error(lang('error update ticket options'), $ticket->getSummary()); + } // try + } // if + $this->redirectToUrl($ticket->getViewUrl()); + } // update_options + + /** + * Close specific ticket + * + * @access public + * @param void + * @return null + */ + function close() { + $ticket = ProjectTickets::findById(get_id()); + if(!($ticket instanceof ProjectTicket)) { + flash_error(lang('ticket dnx')); + $this->redirectTo('trac'); + } // if + + if(!$ticket->canChangeStatus(logged_user())) { + flash_error(lang('no access permissions')); + $this->redirectToReferer(get_url('trac')); + } // if + + $status = $ticket->isClosed() ? 'closed' : 'open'; + + try { + DB::beginWork(); + $ticket->closeTicket(); + ApplicationLogs::createLog($ticket, active_project(), ApplicationLogs::ACTION_CLOSE); + DB::commit(); + + if ($status != 'closed') { + $change = new TicketChange(); + $change->setTicketId($ticket->getId()); + $change->setType('status'); + $change->setFromData($status); + $change->setToData('closed'); + $change->save(); + } + + try { + Notifier::ticket($ticket, $ticket->getSubscribers(), 'close_ticket', $ticket->getClosedBy()); + } catch(Exception $e) { + // nothing here, just suppress error... + } // try + + flash_success(lang('success close ticket')); + } catch(Exception $e) { + flash_error(lang('error close ticket')); + DB::rollback(); + } // try + + $this->redirectToUrl(ProjectTickets::getIndexUrl(true)); + } // close + + /** + * Open specific ticket + * + * @access public + * @param void + * @return null + */ + function open() { + $ticket = ProjectTickets::findById(get_id()); + if(!($ticket instanceof ProjectTicket)) { + flash_error(lang('ticket dnx')); + $this->redirectTo('trac'); + } // if + + if(!$ticket->canChangeStatus(logged_user())) { + flash_error(lang('no access permissions')); + $this->redirectToReferer(get_url('trac')); + } // if + + $status = $ticket->isClosed() ? 'closed' : 'open'; + + try { + DB::beginWork(); + $ticket->openTicket(); + ApplicationLogs::createLog($ticket, active_project(), ApplicationLogs::ACTION_OPEN); + DB::commit(); + + if ($status != 'open') { + $change = new TicketChange(); + $change->setTicketId($ticket->getId()); + $change->setType('status'); + $change->setFromData($status); + $change->setToData('open'); + $change->save(); + } + + try { + Notifier::ticket($ticket, $ticket->getSubscribers(), 'open_ticket', logged_user()); + } catch(Exception $e) { + // nothing here, just suppress error... + } // try + + flash_success(lang('success open ticket')); + } catch(Exception $e) { + flash_error(lang('error open ticket')); + DB::rollback(); + } // try + + $this->redirectToUrl(ProjectTickets::getIndexUrl()); + } // open + + /** + * Delete specific ticket + * + * @access public + * @param void + * @return null + */ + function delete() { + $ticket = ProjectTickets::findById(get_id()); + if(!($ticket instanceof ProjectTicket)) { + flash_error(lang('ticket dnx')); + $this->redirectTo('trac'); + } // if + + if(!$ticket->canDelete(logged_user())) { + flash_error(lang('no access permissions')); + $this->redirectTo('trac'); + } // if + + try { + + DB::beginWork(); + $ticket->delete(); + ApplicationLogs::createLog($ticket, $ticket->getProject(), ApplicationLogs::ACTION_DELETE); + DB::commit(); + + flash_success(lang('success deleted ticket', $ticket->getSummary())); + } catch(Exception $e) { + DB::rollback(); + flash_error(lang('error delete ticket')); + } // try + + $this->redirectTo('trac'); + } // delete + + /** + * Add a new category + * + * @access public + * @param void + * @return null + */ + function add_category() { + if(!Category::canAdd(logged_user(), active_project())) { + flash_error(lang('no access permissions')); + $this->redirectToReferer(get_url('trac', 'categories')); + } // if + + $category = new Category(); + $category_data = array_var($_POST, 'category'); + + tpl_assign('category', $category); + tpl_assign('category_data', $category_data); + + if(is_array(array_var($_POST, 'category'))) { + try { + $category->setFromAttributes($category_data); + $category->setProjectId(active_project()->getId()); + + DB::beginWork(); + $category->save(); + + ApplicationLogs::createLog($category, active_project(), ApplicationLogs::ACTION_ADD); + DB::commit(); + + flash_success(lang('success add category', $category->getName())); + $this->redirectTo('trac', 'categories'); + + // Error... + } catch(Exception $e) { + DB::rollback(); + + $category->setNew(true); + tpl_assign('error', $e); + } // try + + } // if + } // add_category + + /** + * Edit specific category + * + * @access public + * @param void + * @return null + */ + function edit_category() { + $this->setTemplate('add_category'); + + $category = Categories::findById(get_id()); + if(!($category instanceof Category)) { + flash_error(lang('category dnx')); + $this->redirectTo('trac', 'categories'); + } // if + + if(!$category->canView(logged_user())) { + flash_error(lang('no access permissions')); + $this->redirectToReferer(get_url('trac', 'categories')); + } // if + + $category_data = array_var($_POST, 'category'); + if(!is_array($category_data)) { + $category_data = array( + 'name' => $category->getName(), + 'description' => $category->getDescription() + ); // array + } // if + + tpl_assign('category', $category); + tpl_assign('category_data', $category_data); + + if(is_array(array_var($_POST, 'category'))) { + if(!$category->canEdit(logged_user())) { + flash_error(lang('no access permissions')); + $this->redirectTo('trac', 'categories'); + } else { + try { + $category->setFromAttributes($category_data); + + DB::beginWork(); + $category->save(); + + ApplicationLogs::createLog($category, $category->getProject(), ApplicationLogs::ACTION_EDIT); + DB::commit(); + + flash_success(lang('success edit category', $category->getName())); + $this->redirectToUrl($category->getViewUrl()); + + } catch(Exception $e) { + DB::rollback(); + tpl_assign('error', $e); + } // try + } // if + } // if + } // edit_category + + /** + * Delete specific category + * + * @access public + * @param void + * @return null + */ + function delete_category() { + $category = Categories::findById(get_id()); + if(!($category instanceof Category)) { + flash_error(lang('category dnx')); + $this->redirectTo('trac', 'categories'); + } // if + + if(!$category->canDelete(logged_user())) { + flash_error(lang('no access permissions')); + $this->redirectToReferer(get_url('trac', 'categories')); + } // if + + try { + + DB::beginWork(); + $category->delete(); + ApplicationLogs::createLog($category, $category->getProject(), ApplicationLogs::ACTION_DELETE); + DB::commit(); + + flash_success(lang('success deleted category', $category->getName())); + } catch(Exception $e) { + DB::rollback(); + flash_error(lang('error delete category')); + } // try + + $this->redirectTo('trac', 'categories'); + } // delete + + // --------------------------------------------------- + // Subscriptions + // --------------------------------------------------- + + /** + * Subscribe to ticket + * + * @param void + * @return null + */ + function subscribe() { + $ticket = ProjectTickets::findById(get_id()); + if(!($ticket instanceof ProjectTicket)) { + flash_error(lang('ticket dnx')); + $this->redirectTo('trac'); + } // if + + if(!$ticket->canView(logged_user())) { + flash_error(lang('no access permissions')); + $this->redirectTo('trac'); + } // if + + if($ticket->subscribeUser(logged_user())) { + flash_success(lang('success subscribe to ticket')); + } else { + flash_error(lang('error subscribe to ticket')); + } // if + $this->redirectToUrl($ticket->getViewUrl()); + } // subscribe + + /** + * Unsubscribe from message + * + * @param void + * @return null + */ + function unsubscribe() { + $ticket = ProjectTickets::findById(get_id()); + if(!($ticket instanceof ProjectTicket)) { + flash_error(lang('ticket dnx')); + $this->redirectTo('trac'); + } // if + + if(!$ticket->canView(logged_user())) { + flash_error(lang('no access permissions')); + $this->redirectTo('trac'); + } // if + + if($ticket->unsubscribeUser(logged_user())) { + flash_success(lang('success unsubscribe to ticket')); + } else { + flash_error(lang('error unsubscribe to ticket')); + } // if + $this->redirectToUrl($ticket->getViewUrl()); + } // unsubscribe + + } // TracController + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/helpers/company_website.php projectpier-0.8.0.3-trackerpatch/application/helpers/company_website.php --- projectpier-0.8.0.3/application/helpers/company_website.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/application/helpers/company_website.php Wed Aug 6 22:00:17 2008 @@ -26,6 +26,7 @@ define('DASHBOARD_TAB_OVERVIEW', 'overview'); define('DASHBOARD_TAB_MY_PROJECTS', 'my_projects'); define('DASHBOARD_TAB_MY_TASKS', 'my_task'); + define('DASHBOARD_TAB_MY_TICKETS', 'my_tickets'); /** * Prepare dashboard tabbed navigation @@ -49,6 +50,11 @@ DASHBOARD_TAB_MY_TASKS, lang('my tasks'), get_url('dashboard', 'my_tasks') + )); + add_tabbed_navigation_item(new TabbedNavigationItem( + DASHBOARD_TAB_MY_TICKETS, + lang('my tickets'), + get_url('dashboard', 'my_tickets') )); tabbed_navigation_set_selected($selected); diff -urN projectpier-0.8.0.3/application/helpers/page.php projectpier-0.8.0.3-trackerpatch/application/helpers/page.php --- projectpier-0.8.0.3/application/helpers/page.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/application/helpers/page.php Wed Aug 6 22:04:53 2008 @@ -525,11 +525,10 @@ $href = get_stylesheet_url($href); } $page = PageDescription::instance(); - $page->addRelLink($href, 'Stylesheet', array( - 'type' => 'text/css', - 'title' => $title, - 'media' => $media - )); // addRelLink + $attributes = array('type' => 'text/css'); + if ($title) $attributes['title'] = $title; + if ($media) $attributes['media'] = $media; + $page->addRelLink($href, 'Stylesheet', $attributes); // addRelLink } // add_stylesheet_to_page /** diff -urN projectpier-0.8.0.3/application/helpers/project_website.php projectpier-0.8.0.3-trackerpatch/application/helpers/project_website.php --- projectpier-0.8.0.3/application/helpers/project_website.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/application/helpers/project_website.php Wed Aug 6 22:00:17 2008 @@ -17,12 +17,13 @@ } BreadCrumbs::instance()->addByFunctionArguments($args); - } // dashboard_crumbs + } // project_crumbs // Tab IDs define('PROJECT_TAB_OVERVIEW', 'overview'); define('PROJECT_TAB_MESSAGES', 'messages'); define('PROJECT_TAB_TASKS', 'tasks'); + define('PROJECT_TAB_TICKETS', 'tickets'); define('PROJECT_TAB_MILESTONES', 'milestones'); define('PROJECT_TAB_FILES', 'files'); define('PROJECT_TAB_TAGS', 'tags'); @@ -52,6 +53,11 @@ get_url('task') )); add_tabbed_navigation_item(new TabbedNavigationItem( + PROJECT_TAB_TICKETS, + lang('tickets'), + get_url('trac') + )); + add_tabbed_navigation_item(new TabbedNavigationItem( PROJECT_TAB_MILESTONES, lang('milestones'), get_url('milestone') @@ -79,6 +85,6 @@ get_url('project', 'people') )); tabbed_navigation_set_selected($selected); - } // dashboard_tabbed_navigation + } // project_tabbed_navigation ?> diff -urN projectpier-0.8.0.3/application/helpers/ticket.php projectpier-0.8.0.3-trackerpatch/application/helpers/ticket.php --- projectpier-0.8.0.3/application/helpers/ticket.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/helpers/ticket.php Wed Aug 6 22:00:17 2008 @@ -0,0 +1,67 @@ + 'selected') : null; + $options[] = option_tag(lang($type), $type, $option_attributes); + } // foreach + return select_box($name, $options, $attributes); + } // select_ticket_type + + /** + * Render select ticket priority + * + * @param string $selected priority of ticket + * @param array $attributes Additional attributes + * @return string + */ + function select_ticket_priority($name, $selected = null, $attributes = null) { + if ($selected == null) $selected = 'minor'; + $types = array('critical', 'major', 'minor', 'trivial'); + $options = array(); + foreach($types as $type) { + $option_attributes = $type == $selected ? array('selected' => 'selected') : null; + $options[] = option_tag(lang($type), $type, $option_attributes); + } // foreach + return select_box($name, $options, $attributes); + } // select_ticket_priority + + /** + * Render select ticket priority + * + * @param Project $project ticket's project to get the categories + * @param int $selected category id of ticket + * @param array $attributes Additional attributes + * @return string + */ + function select_ticket_category($name, $project, $selected = null, $attributes = null) { + $categories = $project->getCategories(); + $option_attributes = $selected ? null: array('selected' => 'selected'); + $options = array(option_tag(lang('none'), 0, $option_attributes)); + if ($categories && count($categories)) { + foreach($categories as $category) { + $option_attributes = $category->getId() == $selected ? array('selected' => 'selected') : null; + $options[] = option_tag($category->getName(), $category->getId(), $option_attributes); + } // foreach + } // if + return select_box($name, $options, $attributes); + } // select_ticket_priority + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/models/ProjectDataObject.class.php projectpier-0.8.0.3-trackerpatch/application/models/ProjectDataObject.class.php --- projectpier-0.8.0.3/application/models/ProjectDataObject.class.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/application/models/ProjectDataObject.class.php Wed Aug 6 22:00:17 2008 @@ -114,6 +114,17 @@ */ protected $attached_files; + // --------------------------------------------------- + // Subscribers + // --------------------------------------------------- + + /** + * Mark this object as subscribible + * + * @var boolean + */ + protected $is_subscribible = false; + /** * Return owner project. If project_id field does not exists NULL is returned * @@ -700,6 +711,30 @@ 'active_project' => $this->getProject()->getId() )); // get_url } // getDetachFileUrl + + /** + * This event is triggered when we attach new files + * + * @param array $files + * @return boolean + */ + function onAttachFiles($files) { + return true; + } // onAttachFiles + + // --------------------------------------------------- + // Subscribible + // --------------------------------------------------- + + /** + * Returns true if users can subscribe to this object + * + * @param void + * @return boolean + */ + function isSubscribible() { + return (boolean) $this->is_subscribible; + } // isSubscribible // --------------------------------------------------- // System diff -urN projectpier-0.8.0.3/application/models/notifier/Notifier.class.php projectpier-0.8.0.3-trackerpatch/application/models/notifier/Notifier.class.php --- projectpier-0.8.0.3/application/models/notifier/Notifier.class.php Sun Feb 24 17:29:56 2008 +++ projectpier-0.8.0.3-trackerpatch/application/models/notifier/Notifier.class.php Wed Aug 6 22:12:21 2008 @@ -95,6 +95,82 @@ tpl_fetch(get_template_path('new_message', 'notifier')) ); // send } // newMessage + + /** + * Send ticket notification to the list of users ($people) + * + * @param ProjectTicket $ticket New ticket + * @param array $people + * @param string $template template to send notification + * @param User $user user who send the notification + * @return boolean + * @throws NotifierConnectionError + */ + static function ticket(ProjectTicket $ticket, $people, $template, $user) { + if(!is_array($people) || !count($people)) { + return; // nothing here... + } // if + + $recepients = array(); + foreach($people as $subscriber) { + if($subscriber->getId() == $user->getId()) { + continue; // skip comment author + } // if + + $recepients[] = self::prepareEmailAddress($subscriber->getEmail(), $subscriber->getDisplayName()); + } // foreach + + if(!count($recepients)) { + return true; // no recepients + } // if + + tpl_assign('ticket', $ticket); + + return self::sendEmail( + $recepients, + self::prepareEmailAddress($user->getEmail(), $user->getDisplayName()), + $ticket->getProject()->getName() . ' - ' . $ticket->getSummary(), + tpl_fetch(get_template_path($template, 'notifier')) + ); // send + } // ticket + + /** + * Send some files attached to ticket notification to ticket subscribers + * + * @param ProjectTicket $ticket + * @param array $attached_files Files attached to ticket + * @return boolean + * @throws NotifierConnectionError + */ + static function attachFilesToTicket(ProjectTicket $ticket, $attached_files) { + $all_subscribers = $ticket->getSubscribers(); + if(!is_array($all_subscribers)) { + return true; // no subscribers + } // if + + $recepients = array(); + foreach($all_subscribers as $subscriber) { + if($subscriber->getId() == $ticket->getUpdatedById()) { + continue; // skip comment author + } // if + + $recepients[] = self::prepareEmailAddress($subscriber->getEmail(), $subscriber->getDisplayName()); + } // foreach + + if(!count($recepients)) { + return true; // no recepients + } // if + + tpl_assign('ticket', $ticket); + tpl_assign('attached_files', $attached_files); + + return self::sendEmail( + $recepients, + self::prepareEmailAddress($ticket->getUpdatedBy()->getEmail(), $ticket->getUpdatedBy()->getDisplayName()), + $ticket->getProject()->getName() . ' - ' . $ticket->getSummary(), + tpl_fetch(get_template_path('attach_files_ticket', 'notifier')) + ); // send + } // attachFilesToTicket /** * Send new comment notification to message subscriber @@ -109,7 +185,36 @@ throw new Error('Invalid comment object'); } // if - $all_subscribers = $message->getSubscribers(); + return self::newComment($comment, $message->getSubscribers()); + } // newMessageComment + + /** + * Send new comment notification to ticket subscriber + * + * @param TicketComment $comment + * @return boolean + * @throws NotifierConnectionError + */ + static function newTicketComment(Comment $comment) { + $ticket = $comment->getObject(); + if(!($ticket instanceof ProjectTicket)) { + throw new Error('Invalid comment object'); + } // if + + return self::newComment($comment, $ticket->getSubscribers()); + } // newTicketComment + + /** + * Send new comment notification to subscribers + * + * @access private + * @param Comment $comment + * @param string $title title of object for subject + * @param array $all_subscribers subscribers + * @return boolean + * @throws NotifierConnectionError + */ + static function newComment(Comment $comment, $all_subscribers) { if (!is_array($all_subscribers)) { return true; // no subscribers } // if @@ -138,10 +243,10 @@ return self::sendEmail( $recipients, self::prepareEmailAddress($comment->getCreatedBy()->getEmail(), $comment->getCreatedByDisplayName()), - $comment->getProject()->getName() . ' - ' . $message->getTitle(), + $comment->getProject()->getName() . ' - ' . $comment->getObject()->getTitle(), tpl_fetch(get_template_path('new_comment', 'notifier')) ); // send - } // newMessageComment + } // newComment // --------------------------------------------------- // Milestone diff -urN projectpier-0.8.0.3/application/models/project_categories/Categories.class.php projectpier-0.8.0.3-trackerpatch/application/models/project_categories/Categories.class.php --- projectpier-0.8.0.3/application/models/project_categories/Categories.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/models/project_categories/Categories.class.php Wed Aug 6 22:00:17 2008 @@ -0,0 +1,27 @@ + array('`project_id` = ?', $project->getId()), + 'order' => '`name`', + )); // findAll + } // getProjectCategories + + } // Categories + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/models/project_categories/Category.class.php projectpier-0.8.0.3-trackerpatch/application/models/project_categories/Category.class.php --- projectpier-0.8.0.3/application/models/project_categories/Category.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/models/project_categories/Category.class.php Wed Aug 6 22:00:17 2008 @@ -0,0 +1,186 @@ +getProjectId()); + } // getProject + + /** + * Return shortened description + * + * @access public + * @param void + * @return string + */ + function getShortDescription() { + $return = substr_utf($this->getDescription(), 0, 50); + return strlen_utf($this->getDescription()) > 50 ? $return . '...' : $return; + } // getShortDescription + + // --------------------------------------------------- + // Permissions + // --------------------------------------------------- + + /** + * Return true if $user can view this category + * + * @param User $user + * @return boolean + */ + function canView(User $user) { + if(!$user->isProjectUser($this->getProject())) { + return false; // user have access to project + } // if + return true; + } // canView + + /** + * Check if user can add categories in specific project + * + * @param User $user + * @param Project $project + * @return boolean + */ + function canAdd(User $user, Project $project) { + if(!$user->isProjectUser($project)) { + return false; // user is on project + } // if + if($user->isAdministrator()) { + return true; // user is administrator or root + } // if + return $user->getProjectPermission($project, ProjectUsers::CAN_MANAGE_TICKETS); + } // canAdd + + /** + * Check if specific user can update this category + * + * @param User $user + * @return boolean + */ + function canEdit(User $user) { + if(!$user->isProjectUser($this->getProject())) { + return false; // user is on project + } // if + if($user->isAdministrator()) { + return true; // user is administrator or root + } // if + return $user->getProjectPermission($this->getProject(), ProjectUsers::CAN_MANAGE_TICKETS); + } // canEdit + + /** + * Check if specific user can delete this + * + * @param User $user + * @return boolean + */ + function canDelete(User $user) { + if(!$user->isProjectUser($this->getProject())) { + return false; // user is on project + } // if + if($user->isAdministrator()) { + return true; // user is administrator or root + } // if + return $user->getProjectPermission($this->getProject(), ProjectUsers::CAN_MANAGE_TICKETS); + } // canDelete + + // --------------------------------------------------- + // URLs + // --------------------------------------------------- + + /** + * Return tag URL + * + * @param void + * @return string + */ + function getViewUrl() { + return $this->getEditUrl(); + } // getViewUrl + + /** + * Return edit URL + * + * @param void + * @return string + */ + function getEditUrl() { + return get_url('trac', 'edit_category', array('id' => $this->getId(), 'active_project' => $this->getProjectId())); + } // getEditUrl + + /** + * Return delete URL + * + * @param void + * @return string + */ + function getDeleteUrl() { + return get_url('trac', 'delete_category', array('id' => $this->getId(), 'active_project' => $this->getProjectId())); + } // getDeleteUrl + + // --------------------------------------------------- + // System + // --------------------------------------------------- + + /** + * Validate before save + * + * @param array $error + * @return null + */ + function validate(&$errors) { + if(!$this->validatePresenceOf('name')) { + $errors[] = lang('category name required'); + } // if + } // validate + + // --------------------------------------------------- + // ApplicationDataObject implementation + // --------------------------------------------------- + + /** + * Return object name + * + * @param void + * @return string + */ + function getObjectName() { + return $this->getName(); + } // getObjectName + + /** + * Return object type name + * + * @param void + * @return string + */ + function getObjectTypeName() { + return lang('category'); + } // getObjectTypeName + + /** + * Return view tag URL + * + * @param void + * @return string + */ + function getObjectUrl() { + return $this->getViewUrl(); + } // getObjectUrl + + } // Comment + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/models/project_categories/base/BaseCategories.class.php projectpier-0.8.0.3-trackerpatch/application/models/project_categories/base/BaseCategories.class.php --- projectpier-0.8.0.3/application/models/project_categories/base/BaseCategories.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/models/project_categories/base/BaseCategories.class.php Wed Aug 6 22:00:17 2008 @@ -0,0 +1,220 @@ + Column type map + * + * @var array + * @static + */ + static private $columns = array('id' => DATA_TYPE_INTEGER, 'project_id' => DATA_TYPE_INTEGER, 'name' => DATA_TYPE_STRING, 'description' => DATA_TYPE_STRING); + + /** + * Construct + * + * @return BaseCategories + */ + function __construct() { + parent::__construct('Category', 'project_categories', true); + } // __construct + + // ------------------------------------------------------- + // Description methods + // ------------------------------------------------------- + + /** + * Return array of object columns + * + * @access public + * @param void + * @return array + */ + function getColumns() { + return array_keys(self::$columns); + } // getColumns + + /** + * Return column type + * + * @access public + * @param string $column_name + * @return string + */ + function getColumnType($column_name) { + if(isset(self::$columns[$column_name])) { + return self::$columns[$column_name]; + } else { + return DATA_TYPE_STRING; + } // if + } // getColumnType + + /** + * Return array of PK columns. If only one column is PK returns its name as string + * + * @access public + * @param void + * @return array or string + */ + function getPkColumns() { + return 'id'; + } // getPkColumns + + /** + * Return name of first auto_incremenent column if it exists + * + * @access public + * @param void + * @return string + */ + function getAutoIncrementColumn() { + return 'id'; + } // getAutoIncrementColumn + + // ------------------------------------------------------- + // Finders + // ------------------------------------------------------- + + /** + * Do a SELECT query over database with specified arguments + * + * @access public + * @param array $arguments Array of query arguments. Fields: + * + * - one - select first row + * - conditions - additional conditions + * - order - order by string + * - offset - limit offset, valid only if limit is present + * - limit + * + * @return one or Categories objects + * @throws DBQueryError + */ + function find($arguments = null) { + if(isset($this) && instance_of($this, 'Categories')) { + return parent::find($arguments); + } else { + return Categories::instance()->find($arguments); + } // if + } // find + + /** + * Find all records + * + * @access public + * @param array $arguments + * @return one or Categories objects + */ + function findAll($arguments = null) { + if(isset($this) && instance_of($this, 'Categories')) { + return parent::findAll($arguments); + } else { + return Categories::instance()->findAll($arguments); + } // if + } // findAll + + /** + * Find one specific record + * + * @access public + * @param array $arguments + * @return Category + */ + function findOne($arguments = null) { + if(isset($this) && instance_of($this, 'Categories')) { + return parent::findOne($arguments); + } else { + return Categories::instance()->findOne($arguments); + } // if + } // findOne + + /** + * Return object by its PK value + * + * @access public + * @param mixed $id + * @param boolean $force_reload If true cache will be skipped and data will be loaded from database + * @return Category + */ + function findById($id, $force_reload = false) { + if(isset($this) && instance_of($this, 'Categories')) { + return parent::findById($id, $force_reload); + } else { + return Categories::instance()->findById($id, $force_reload); + } // if + } // findById + + /** + * Return number of rows in this table + * + * @access public + * @param string $conditions Query conditions + * @return integer + */ + function count($condition = null) { + if(isset($this) && instance_of($this, 'Categories')) { + return parent::count($condition); + } else { + return Categories::instance()->count($condition); + } // if + } // count + + /** + * Delete rows that match specific conditions. If $conditions is NULL all rows from table will be deleted + * + * @access public + * @param string $conditions Query conditions + * @return boolean + */ + function delete($condition = null) { + if(isset($this) && instance_of($this, 'Categories')) { + return parent::delete($condition); + } else { + return Categories::instance()->delete($condition); + } // if + } // delete + + /** + * This function will return paginated result. Result is an array where first element is + * array of returned object and second populated pagination object that can be used for + * obtaining and rendering pagination data using various helpers. + * + * Items and pagination array vars are indexed with 0 for items and 1 for pagination + * because you can't use associative indexing with list() construct + * + * @access public + * @param array $arguments Query argumens (@see find()) Limit and offset are ignored! + * @param integer $items_per_page Number of items per page + * @param integer $current_page Current page number + * @return array + */ + function paginate($arguments = null, $items_per_page = 10, $current_page = 1) { + if(isset($this) && instance_of($this, 'Categories')) { + return parent::paginate($arguments, $items_per_page, $current_page); + } else { + return Categories::instance()->paginate($arguments, $items_per_page, $current_page); + } // if + } // paginate + + /** + * Return manager instance + * + * @return Categories + */ + function instance() { + static $instance; + if(!instance_of($instance, 'Categories')) { + $instance = new Categories(); + } // if + return $instance; + } // instance + + } // Categories + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/models/project_categories/base/BaseCategory.class.php projectpier-0.8.0.3-trackerpatch/application/models/project_categories/base/BaseCategory.class.php --- projectpier-0.8.0.3/application/models/project_categories/base/BaseCategory.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/models/project_categories/base/BaseCategory.class.php Wed Aug 6 22:00:17 2008 @@ -0,0 +1,117 @@ +getColumnValue('id'); + } // getId() + + /** + * Set value of 'id' field + * + * @access public + * @param integer $value + * @return boolean + */ + function setId($value) { + return $this->setColumnValue('id', $value); + } // setId() + + /** + * Return value of 'project_id' field + * + * @access public + * @param void + * @return integer + */ + function getProjectId() { + return $this->getColumnValue('project_id'); + } // getProjectId() + + /** + * Set value of 'project_id' field + * + * @access public + * @param integer $value + * @return boolean + */ + function setProjectId($value) { + return $this->setColumnValue('project_id', $value); + } // setProjectId() + + /** + * Return value of 'name' field + * + * @access public + * @param void + * @return integer + */ + function getName() { + return $this->getColumnValue('name'); + } // getName() + + /** + * Set value of 'name' field + * + * @access public + * @param integer $value + * @return boolean + */ + function setName($value) { + return $this->setColumnValue('name', $value); + } // setName() + + /** + * Return value of 'description' field + * + * @access public + * @param void + * @return string + */ + function getDescription() { + return $this->getColumnValue('description'); + } // getDescription() + + /** + * Set value of 'description' field + * + * @access public + * @param string $value + * @return boolean + */ + function setDescription($value) { + return $this->setColumnValue('description', $value); + } // setDescription() + + + /** + * Return manager instance + * + * @access protected + * @param void + * @return Comments + */ + function manager() { + if(!($this->manager instanceof Categories)) $this->manager = Categories::instance(); + return $this->manager; + } // manager + + } // BaseComment + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/models/project_messages/ProjectMessage.class.php projectpier-0.8.0.3-trackerpatch/application/models/project_messages/ProjectMessage.class.php --- projectpier-0.8.0.3/application/models/project_messages/ProjectMessage.class.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/application/models/project_messages/ProjectMessage.class.php Wed Aug 6 22:19:27 2008 @@ -42,7 +42,14 @@ * @var boolean */ protected $is_file_container = true; - + + /** + * Message is subscribible + * + * @var boolean + */ + protected $is_subscribible = true; + /** * Cached array of subscribers * diff -urN projectpier-0.8.0.3/application/models/project_tickets/ProjectTicket.class.php projectpier-0.8.0.3-trackerpatch/application/models/project_tickets/ProjectTicket.class.php --- projectpier-0.8.0.3/application/models/project_tickets/ProjectTicket.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/models/project_tickets/ProjectTicket.class.php Wed Aug 6 22:00:17 2008 @@ -0,0 +1,654 @@ +setUpdated('comment'); + $this->save(); + + $change = new TicketChange(); + $change->setTicketId($this->getId()); + $change->setType('comment'); + $change->setToData('#'.$this->countAllComments()); + $change->save(); + + Notifier::newTicketComment($comment); + } catch(Exception $e) { + // nothing here, just suppress error... + } // try + } // onAddComment + + // --------------------------------------------------- + // Files + // --------------------------------------------------- + + /** + * Handle on add comment event + * + * @param array $files Attached files + * @return null + */ + function onAttachFiles($files) { + try { + $this->setUpdated('attachment'); + $this->save(); + + foreach ($files as $file) { + $change = new TicketChange(); + $change->setTicketId($this->getId()); + $change->setType('attachment'); + $change->setToData($file->getFilename()); + $change->save(); + } // foreach + + Notifier::attachFilesToTicket($this, $files); + } catch(Exception $e) { + // nothing here, just suppress error... + } // try + } // onAttachFiles + + // --------------------------------------------------- + // Changes + // --------------------------------------------------- + + /** + * Return array of changes + * + * @param void + * @return array + */ + function getChanges() { + if(is_null($this->changes)) $this->changes = TicketChanges::getChangesByTicket($this); + return $this->changes; + } // getChanges + + // --------------------------------------------------- + // Subscriptions + // --------------------------------------------------- + + /** + * Return array of subscribers + * + * @param void + * @return array + */ + function getSubscribers() { + if(is_null($this->subscribers)) $this->subscribers = TicketSubscriptions::getUsersByTicket($this); + return $this->subscribers; + } // getSubscribers + + /** + * Check if specific user is subscriber + * + * @param User $user + * @return boolean + */ + function isSubscriber(User $user) { + $subscription = TicketSubscriptions::findById(array( + 'ticket_id' => $this->getId(), + 'user_id' => $user->getId() + )); // findById + return $subscription instanceof TicketSubscription; + } // isSubscriber + + /** + * Subscribe specific user to this ticket + * + * @param User $user + * @return boolean + */ + function subscribeUser(User $user) { + if($this->isNew()) { + throw new Error('Can\'t subscribe user to ticket that is not saved'); + } // if + if($this->isSubscriber($user)) { + return true; + } // if + + // New subscription + $subscription = new TicketSubscription(); + $subscription->setTicketId($this->getId()); + $subscription->setUserId($user->getId()); + return $subscription->save(); + } // subscribeUser + + /** + * Unsubscribe user + * + * @param User $user + * @return boolean + */ + function unsubscribeUser(User $user) { + $subscription = TicketSubscriptions::findById(array( + 'ticket_id' => $this->getId(), + 'user_id' => $user->getId() + )); // findById + if($subscription instanceof TicketSubscription) { + return $subscription->delete(); + } else { + return true; + } // if + } // unsubscribeUser + + /** + * Clear all ticket subscriptions + * + * @param void + * @return boolean + */ + function clearSubscriptions() { + return TicketSubscriptions::clearByTicket($this); + } // clearSubscriptions + + // --------------------------------------------------- + // Operations + // --------------------------------------------------- + + /** + * Return object name + * + * @access public + * @param void + * @return string + */ + function getTitle() { + return $this->getSummary(); + } // getObjectName + + /** + * Return owner project obj + * + * @access public + * @param void + * @return Project + */ + function getProject() { + return Projects::findById($this->getProjectId()); + } // getProject + + /** + * Return user object of person who created this ticket + * + * @access public + * @param void + * @return User + */ + function getClosedBy() { + return Users::findById($this->getClosedById()); + } // getCreatedBy + + /** + * Return owner user or company + * + * @access public + * @param void + * @return ApplicationDataObject + */ + function getAssignedTo() { + if($this->getAssignedToUserId() > 0) { + return $this->getAssignedToUser(); + } elseif($this->getAssignedToCompanyId() > 0) { + return $this->getAssignedToCompany(); + } else { + return null; + } // if + } // getAssignedTo + + /** + * Return owner comapny + * + * @access public + * @param void + * @return Company + */ + function getAssignedToCompany() { + return Companies::findById($this->getAssignedToCompanyId()); + } // getAssignedToCompany + + /** + * Return owner user + * + * @access public + * @param void + * @return User + */ + function getAssignedToUser() { + return Users::findById($this->getAssignedToUserId()); + } // getAssignedToUser + + /** + * Return owner user or company + * + * @access public + * @param void + * @return ApplicationDataObject + */ + function getCategory() { + if($this->getCategoryId() > 0) { + return Categories::findById($this->getCategoryId()); + } else { + return null; + } // if + } // getAssignedTo + + /** + * Return status of ticket + * + * @access public + * @param void + * @return boolean + */ + function getStatus() { + return $this->isClosed() ? 'closed' : 'open'; + } // getStatus + + /** + * Returns true if this ticket was not closed + * + * @access public + * @param void + * @return boolean + */ + function isOpen() { + return !$this->isClosed(); + } // isOpen + + /** + * Returns true if this ticket is closed + * + * @access public + * @param void + * @return boolean + */ + function isClosed() { + return $this->getClosedOn() instanceof DateTimeValue; + } // isClosed + + // --------------------------------------------------- + // Permissions + // --------------------------------------------------- + + /** + * Returns true if $user can access this ticket + * + * @param User $user + * @return boolean + */ + function canView(User $user) { + if(!$user->isProjectUser($this->getProject())) { + return false; // user have access to project + } // if + if($this->isPrivate() && !$user->isMemberOfOwnerCompany()) { + return false; // user that is not member of owner company can't access private objects + } // if + return true; + } // canView + + /** + * Check if specific user can add tickets to specific project + * + * @access public + * @param User $user + * @param Project $project + * @return booelean + */ + function canAdd(User $user, Project $project) { + if(!$user->isProjectUser($project)) { + return false; // user is on project + } // if + return true; + } // canAdd + + /** + * Check if specific user can update this ticket + * + * @access public + * @param User $user + * @return boolean + */ + function canChangeStatus(User $user) { + if(!$user->isProjectUser($this->getProject())) { + return false; + } // if + if($this->canEdit($user)) { + return true; + } // if + + return $user->getId() == $this->getCreatedById(); + } // canEdit + + /** + * Check if specific user can update this ticket + * + * @access public + * @param User $user + * @return boolean + */ + function canEdit(User $user) { + if(!$user->isProjectUser($this->getProject())) { + return false; + } // if + if($user->isAdministrator()) { + return true; + } // if + if($this->isPrivate() && !$user->isMemberOfOwnerCompany()) { + return false; // user that is not member of owner company can't access private objects + } // if + + $assigned_to = $this->getAssignedTo(); + if($assigned_to instanceof User) { + if($user->getId() == $assigned_to->getId()) { + return true; + } // if + } elseif($assigned_to instanceof Company) { + if($user->getCompanyId() == $assigned_to->getId()) { + return true; + } // if + } // if + + return $user->getProjectPermission($this->getProject(), ProjectUsers::CAN_MANAGE_TICKETS); + } // canEdit + + /** + * Check if $user can update message options + * + * @param User $user + * @return boolean + */ + function canUpdateOptions(User $user) { + return $user->isMemberOfOwnerCompany() && $this->canEdit($user); + } // canUpdateOptions + + /** + * Check if specific user can delete this task + * + * @access public + * @param User $user + * @return boolean + */ + function canDelete(User $user) { + if(!$user->isProjectUser($this->getProject())) { + return false; + } // if + if($user->isAdministrator()) { + return true; + } // if + + return false; // no no + } // canDelete + + // --------------------------------------------------- + // Operations + // --------------------------------------------------- + + /** + * Complete this task and check if we need to complete the list + * + * @access public + * @param void + * @return null + */ + function closeTicket() { + $this->setClosedOn(DateTimeValueLib::now()); + $this->setClosedById(logged_user()->getId()); + $this->setUpdated('closed'); + $this->save(); + } // completeTask + + /** + * Open this task and check if we need to reopen list again + * + * @access public + * @param void + * @return null + */ + function openTicket() { + $this->setClosedOn(null); + $this->setClosedById(0); + $this->setUpdated('open'); + $this->save(); + } // openTask + + // --------------------------------------------------- + // URLs + // --------------------------------------------------- + + /** + * Return view ticket URL + * + * @access public + * @param void + * @return string + */ + function getViewUrl() { + return $this->getEditUrl(); + } // getViewUrl + + /** + * Return edit task URL + * + * @access public + * @param void + * @return string + */ + function getEditUrl() { + return get_url('trac', 'edit', array('id' => $this->getId(), 'active_project' => $this->getProjectId())); + } // getEditUrl + + /** + * Return delete task URL + * + * @access public + * @param void + * @return string + */ + function getDeleteUrl() { + return get_url('trac', 'delete', array('id' => $this->getId(), 'active_project' => $this->getProjectId())); + } // getDeleteUrl + + /** + * Return comete task URL + * + * @access public + * @param string $redirect_to Redirect to this URL (referer will be used if this URL is not provided) + * @return string + */ + function getCloseUrl($redirect_to = null) { + $params = array( + 'id' => $this->getId(), + 'active_project' => $this->getProjectId() + ); // array + + if(trim($redirect_to)) { + $params['redirect_to'] = $redirect_to; + } // if + + return get_url('trac', 'close', $params); + } // getCompleteUrl + + /** + * Return open task URL + * + * @access public + * @param string $redirect_to Redirect to this URL (referer will be used if this URL is not provided) + * @return string + */ + function getOpenUrl($redirect_to = null) { + $params = array( + 'id' => $this->getId(), + 'active_project' => $this->getProjectId() + ); // array + + if(trim($redirect_to)) { + $params['redirect_to'] = $redirect_to; + } // if + + return get_url('trac', 'open', $params); + } // getOpenUrl + + /** + * Return update options URL + * + * @param void + * @return string + */ + function getUpdateOptionsUrl() { + return get_url('trac', 'update_options', array('id' => $this->getId(), 'active_project' => $this->getProjectId())); + } // getUpdateOptionsUrl + + /** + * Return subscribe URL + * + * @param void + * @return boolean + */ + function getSubscribeUrl() { + return get_url('trac', 'subscribe', array('id' => $this->getId(), 'active_project' => $this->getProjectId())); + } // getSubscribeUrl + + /** + * Return unsubscribe URL + * + * @param void + * @return boolean + */ + function getUnsubscribeUrl() { + return get_url('trac', 'unsubscribe', array('id' => $this->getId(), 'active_project' => $this->getProjectId())); + } // getUnsubscribeUrl + + // --------------------------------------------------- + // System + // --------------------------------------------------- + + /** + * Validate before save + * + * @access public + * @param array $errors + * @return null + */ + function validate(&$errors) { + if(!$this->validatePresenceOf('summary')) $errors[] = lang('ticket summary required'); + if(!$this->validatePresenceOf('description')) $errors[] = lang('ticket description required'); + } // validate + + /** + * Delete this task + * + * @access public + * @param void + * @return boolean + */ + function delete() { + $comments = $this->getComments(); + if(is_array($comments)) foreach($comments as $comment) $comment->delete(); + + $this->clearSubscriptions(); + return parent::delete(); + } // delete + + // --------------------------------------------------- + // ApplicationDataObject implementation + // --------------------------------------------------- + + /** + * Return object name + * + * @access public + * @param void + * @return string + */ + function getObjectName() { + return $this->getSummary(); + } // getObjectName + + /** + * Return object type name + * + * @param void + * @return string + */ + function getObjectTypeName() { + return lang('ticket'); + } // getObjectTypeName + + /** + * Return object URl + * + * @access public + * @param void + * @return string + */ + function getObjectUrl() { + return $this->getViewUrl(); + } // getObjectUrl + + } // ProjectTicket + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/models/project_tickets/ProjectTickets.class.php projectpier-0.8.0.3-trackerpatch/application/models/project_tickets/ProjectTickets.class.php --- projectpier-0.8.0.3/application/models/project_tickets/ProjectTickets.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/models/project_tickets/ProjectTickets.class.php Wed Aug 6 22:00:17 2008 @@ -0,0 +1,89 @@ +getId()); + } else { + $conditions = array('`project_id` = ? AND `is_private` = ?', $project->getId(), false); + } // if + + return self::findAll(array( + 'conditions' => $conditions, + 'order' => '`created_on` DESC', + )); // findAll + } // getProjectTickets + + /** + * Return open tickets for specific project + * + * @param Project $project + * @param boolean $include_private Include private tickets + * @return array + */ + static function getOpenProjectTickets(Project $project, $include_private = false) { + if($include_private) { + $conditions = array('`project_id` = ? AND `closed_on` = ?', $project->getId(), EMPTY_DATETIME); + } else { + $conditions = array('`project_id` = ? AND `closed_on` = ? AND `is_private` = ?', $project->getId(), EMPTY_DATETIME, false); + } // if + + return self::findAll(array( + 'conditions' => $conditions, + 'order' => '`created_on` DESC', + )); // findAll + } // getOpenProjectTickets + + /** + * Return closed tickets for specific project + * + * @param Project $project + * @param boolean $include_private Include private tickets + * @return array + */ + static function getClosedProjectTickets(Project $project, $include_private = false) { + if($include_private) { + $conditions = array('`project_id` = ? AND `closed_on` > ?', $project->getId(), EMPTY_DATETIME); + } else { + $conditions = array('`project_id` = ? AND `closed_on` > ? AND `is_private` = ?', $project->getId(), EMPTY_DATETIME, false); + } // if + + return self::findAll(array( + 'conditions' => $conditions, + 'order' => '`created_on` DESC', + )); // findAll + } // getClosedProjectTickets + + /** + * Return trac index page + * + * @param string $order_by + * @param integer $page + * @return string + */ + static function getIndexUrl($closed = false) { + if ($closed) { + $options = array('closed' => true); + } else { + $options = array(); + } // if + return get_url('trac', 'index', $options); + } // getIndexUrl + + } // ProjectTickets + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/models/project_tickets/base/BaseProjectTicket.class.php projectpier-0.8.0.3-trackerpatch/application/models/project_tickets/base/BaseProjectTicket.class.php --- projectpier-0.8.0.3/application/models/project_tickets/base/BaseProjectTicket.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/models/project_tickets/base/BaseProjectTicket.class.php Wed Aug 6 22:00:17 2008 @@ -0,0 +1,403 @@ +getColumnValue('id'); + } // getId() + + /** + * Set value of 'id' field + * + * @access public + * @param integer $value + * @return boolean + */ + function setId($value) { + return $this->setColumnValue('id', $value); + } // setId() + + /** + * Return value of 'project_id' field + * + * @access public + * @param void + * @return integer + */ + function getProjectId() { + return $this->getColumnValue('project_id'); + } // getProjectId() + + /** + * Set value of 'project_id' field + * + * @access public + * @param integer $value + * @return boolean + */ + function setProjectId($value) { + return $this->setColumnValue('project_id', $value); + } // setProjectId() + + /** + * Return value of 'category_id' field + * + * @access public + * @param void + * @return integer + */ + function getCategoryId() { + return $this->getColumnValue('category_id'); + } // getCategoryId() + + /** + * Set value of 'category_id' field + * + * @access public + * @param integer $value + * @return boolean + */ + function setCategoryId($value) { + return $this->setColumnValue('category_id', $value); + } // setCategoryId() + + /** + * Return value of 'created_by_id' field + * + * @access public + * @param void + * @return integer + */ + function getCreatedById() { + return $this->getColumnValue('created_by_id'); + } // getCreatedById() + + /** + * Set value of 'created_by_id' field + * + * @access public + * @param integer $value + * @return boolean + */ + function setCreatedById($value) { + return $this->setColumnValue('created_by_id', $value); + } // setCreatedById() + + /** + * Return value of 'closed_by_id' field + * + * @access public + * @param void + * @return integer + */ + function getClosedById() { + return $this->getColumnValue('closed_by_id'); + } // getClosedById() + + /** + * Set value of 'closed_by_id' field + * + * @access public + * @param integer $value + * @return boolean + */ + function setClosedById($value) { + return $this->setColumnValue('closed_by_id', $value); + } // getClosedById() + + /** + * Return value of 'assigned_to_company_id' field + * + * @access public + * @param void + * @return integer + */ + function getAssignedToCompanyId() { + return $this->getColumnValue('assigned_to_company_id'); + } // getAssignedToCompanyId() + + /** + * Set value of 'assigned_to_company_id' field + * + * @access public + * @param integer $value + * @return boolean + */ + function setAssignedToCompanyId($value) { + return $this->setColumnValue('assigned_to_company_id', $value); + } // setAssignedToCompanyId() + + /** + * Return value of 'assigned_to_user_id' field + * + * @access public + * @param void + * @return integer + */ + function getAssignedToUserId() { + return $this->getColumnValue('assigned_to_user_id'); + } // getAssignedToUserId() + + /** + * Set value of 'assigned_to_user_id' field + * + * @access public + * @param integer $value + * @return boolean + */ + function setAssignedToUserId($value) { + return $this->setColumnValue('assigned_to_user_id', $value); + } // setAssignedToUserId() + + /** + * Return value of 'summary' field + * + * @access public + * @param void + * @return string + */ + function getSummary() { + return $this->getColumnValue('summary'); + } // getSummary() + + /** + * Set value of 'summary' field + * + * @access public + * @param string $value + * @return boolean + */ + function setSummary($value) { + return $this->setColumnValue('summary', $value); + } // setSummary() + + /** + * Return value of 'type' field + * + * @access public + * @param void + * @return string + */ + function getType() { + return $this->getColumnValue('type'); + } // getType() + + /** + * Set value of 'type' field + * + * @access public + * @param string $value + * @return boolean + */ + function setType($value) { + return $this->setColumnValue('type', $value); + } // setType() + + /** + * Return value of 'description' field + * + * @access public + * @param void + * @return string + */ + function getDescription() { + return $this->getColumnValue('description'); + } // getDescription() + + /** + * Set value of 'description' field + * + * @access public + * @param string $value + * @return boolean + */ + function setDescription($value) { + return $this->setColumnValue('description', $value); + } // setDescription() + + /** + * Return value of 'priority' field + * + * @access public + * @param void + * @return string + */ + function getPriority() { + return $this->getColumnValue('priority'); + } // getPriority() + + /** + * Set value of 'priority' field + * + * @access public + * @param string $value + * @return boolean + */ + function setPriority($value) { + return $this->setColumnValue('priority', $value); + } // setPriority() + + /** + * Return value of 'created_on' field + * + * @access public + * @param void + * @return DateTimeValue + */ + function getCreatedOn() { + return $this->getColumnValue('created_on'); + } // getCreatedOn() + + /** + * Set value of 'created_on' field + * + * @access public + * @param DateTimeValue $value + * @return boolean + */ + function setCreatedOn($value) { + return $this->setColumnValue('created_on', $value); + } // setCreatedOn() + + /** + * Return value of 'closed_on' field + * + * @access public + * @param void + * @return DateTimeValue + */ + function getClosedOn() { + return $this->getColumnValue('closed_on'); + } // getClosedOn() + + /** + * Set value of 'closed_on' field + * + * @access public + * @param DateTimeValue $value + * @return boolean + */ + function setClosedOn($value) { + return $this->setColumnValue('closed_on', $value); + } // setClosedOn() + + /** + * Return value of 'updated_on' field + * + * @access public + * @param void + * @return DateTimeValue + */ + function getUpdatedOn() { + return $this->getColumnValue('updated_on'); + } // getUpdatedOn() + + /** + * Set value of 'updated_on' field + * + * @access public + * @param DateTimeValue $value + * @return boolean + */ + function setUpdatedOn($value) { + return $this->setColumnValue('updated_on', $value); + } // setUpdatedOn() + + /** + * Return value of 'updated_by_id' field + * + * @access public + * @param void + * @return integer + */ + function getUpdatedById() { + return $this->getColumnValue('updated_by_id'); + } // getUpdatedById() + + /** + * Set value of 'updated_by_id' field + * + * @access public + * @param integer $value + * @return boolean + */ + function setUpdatedById($value) { + return $this->setColumnValue('updated_by_id', $value); + } // setUpdatedById() + + /** + * Return value of 'updated' field + * + * @access public + * @param void + * @return integer + */ + function getUpdated() { + return $this->getColumnValue('updated'); + } // getUpdated() + + /** + * Set value of 'updated' field + * + * @access public + * @param integer $value + * @return boolean + */ + function setUpdated($value) { + return $this->setColumnValue('updated', $value); + } // setUpdated() + + /** + * Return value of 'is_private' field + * + * @access public + * @param void + * @return boolean + */ + function getIsPrivate() { + return $this->getColumnValue('is_private'); + } // getIsPrivate() + + /** + * Set value of 'is_private' field + * + * @access public + * @param boolean $value + * @return boolean + */ + function setIsPrivate($value) { + return $this->setColumnValue('is_private', $value); + } // setIsPrivate() + + + /** + * Return manager instance + * + * @access protected + * @param void + * @return ProjectTickets + */ + function manager() { + if(!($this->manager instanceof ProjectTickets)) $this->manager = ProjectTickets::instance(); + return $this->manager; + } // manager + + } // BaseProjectTicket + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/models/project_tickets/base/BaseProjectTickets.class.php projectpier-0.8.0.3-trackerpatch/application/models/project_tickets/base/BaseProjectTickets.class.php --- projectpier-0.8.0.3/application/models/project_tickets/base/BaseProjectTickets.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/models/project_tickets/base/BaseProjectTickets.class.php Wed Aug 6 22:00:17 2008 @@ -0,0 +1,220 @@ + Column type map + * + * @var array + * @static + */ + static private $columns = array('id' => DATA_TYPE_INTEGER, 'project_id' => DATA_TYPE_INTEGER, 'category_id' => DATA_TYPE_INTEGER, 'created_by_id' => DATA_TYPE_INTEGER, 'closed_by_id' => DATA_TYPE_INTEGER, 'assigned_to_user_id' => DATA_TYPE_INTEGER, 'assigned_to_company_id' => DATA_TYPE_INTEGER, 'summary' => DATA_TYPE_STRING, 'type' => DATA_TYPE_STRING, 'description' => DATA_TYPE_STRING, 'priority' => DATA_TYPE_STRING, 'created_on' => DATA_TYPE_DATETIME, 'closed_on' => DATA_TYPE_DATETIME, 'updated_on' => DATA_TYPE_DATETIME, 'updated_by_id' => DATA_TYPE_INTEGER, 'updated' => DATA_TYPE_STRING, 'is_private' => DATA_TYPE_BOOLEAN); + + /** + * Construct + * + * @return BaseProjectTickets + */ + function __construct() { + parent::__construct('ProjectTicket', 'project_tickets', true); + } // __construct + + // ------------------------------------------------------- + // Description methods + // ------------------------------------------------------- + + /** + * Return array of object columns + * + * @access public + * @param void + * @return array + */ + function getColumns() { + return array_keys(self::$columns); + } // getColumns + + /** + * Return column type + * + * @access public + * @param string $column_name + * @return string + */ + function getColumnType($column_name) { + if(isset(self::$columns[$column_name])) { + return self::$columns[$column_name]; + } else { + return DATA_TYPE_STRING; + } // if + } // getColumnType + + /** + * Return array of PK columns. If only one column is PK returns its name as string + * + * @access public + * @param void + * @return array or string + */ + function getPkColumns() { + return 'id'; + } // getPkColumns + + /** + * Return name of first auto_incremenent column if it exists + * + * @access public + * @param void + * @return string + */ + function getAutoIncrementColumn() { + return 'id'; + } // getAutoIncrementColumn + + // ------------------------------------------------------- + // Finders + // ------------------------------------------------------- + + /** + * Do a SELECT query over database with specified arguments + * + * @access public + * @param array $arguments Array of query arguments. Fields: + * + * - one - select first row + * - conditions - additional conditions + * - order - order by string + * - offset - limit offset, valid only if limit is present + * - limit + * + * @return one or ProjectTickets objects + * @throws DBQueryError + */ + function find($arguments = null) { + if(isset($this) && instance_of($this, 'ProjectTickets')) { + return parent::find($arguments); + } else { + return ProjectTickets::instance()->find($arguments); + } // if + } // find + + /** + * Find all records + * + * @access public + * @param array $arguments + * @return one or ProjectTickets objects + */ + function findAll($arguments = null) { + if(isset($this) && instance_of($this, 'ProjectTickets')) { + return parent::findAll($arguments); + } else { + return ProjectTickets::instance()->findAll($arguments); + } // if + } // findAll + + /** + * Find one specific record + * + * @access public + * @param array $arguments + * @return ProjectTicket + */ + function findOne($arguments = null) { + if(isset($this) && instance_of($this, 'ProjectTickets')) { + return parent::findOne($arguments); + } else { + return ProjectTickets::instance()->findOne($arguments); + } // if + } // findOne + + /** + * Return object by its PK value + * + * @access public + * @param mixed $id + * @param boolean $force_reload If true cache will be skipped and data will be loaded from database + * @return ProjectTicket + */ + function findById($id, $force_reload = false) { + if(isset($this) && instance_of($this, 'ProjectTickets')) { + return parent::findById($id, $force_reload); + } else { + return ProjectTickets::instance()->findById($id, $force_reload); + } // if + } // findById + + /** + * Return number of rows in this table + * + * @access public + * @param string $conditions Query conditions + * @return integer + */ + function count($condition = null) { + if(isset($this) && instance_of($this, 'ProjectTickets')) { + return parent::count($condition); + } else { + return ProjectTickets::instance()->count($condition); + } // if + } // count + + /** + * Delete rows that match specific conditions. If $conditions is NULL all rows from table will be deleted + * + * @access public + * @param string $conditions Query conditions + * @return boolean + */ + function delete($condition = null) { + if(isset($this) && instance_of($this, 'ProjectTickets')) { + return parent::delete($condition); + } else { + return ProjectTickets::instance()->delete($condition); + } // if + } // delete + + /** + * This function will return paginated result. Result is an array where first element is + * array of returned object and second populated pagination object that can be used for + * obtaining and rendering pagination data using various helpers. + * + * Items and pagination array vars are indexed with 0 for items and 1 for pagination + * because you can't use associative indexing with list() construct + * + * @access public + * @param array $arguments Query argumens (@see find()) Limit and offset are ignored! + * @param integer $items_per_page Number of items per page + * @param integer $current_page Current page number + * @return array + */ + function paginate($arguments = null, $items_per_page = 10, $current_page = 1) { + if(isset($this) && instance_of($this, 'ProjectTickets')) { + return parent::paginate($arguments, $items_per_page, $current_page); + } else { + return ProjectTickets::instance()->paginate($arguments, $items_per_page, $current_page); + } // if + } // paginate + + /** + * Return manager instance + * + * @return ProjectTickets + */ + function instance() { + static $instance; + if(!instance_of($instance, 'ProjectTickets')) { + $instance = new ProjectTickets(); + } // if + return $instance; + } // instance + + } // BaseProjectTickets + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/models/project_users/ProjectUsers.class.php projectpier-0.8.0.3-trackerpatch/application/models/project_users/ProjectUsers.class.php --- projectpier-0.8.0.3/application/models/project_users/ProjectUsers.class.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/application/models/project_users/ProjectUsers.class.php Wed Aug 6 22:27:49 2008 @@ -14,6 +14,7 @@ const CAN_MANAGE_MILESTONES = 'can_manage_milestones'; const CAN_UPLOAD_FILES = 'can_upload_files'; const CAN_MANAGE_FILES = 'can_manage_files'; + const CAN_MANAGE_TICKETS = 'can_manage_tickets'; const CAN_ASSIGN_TO_OWNERS = 'can_assign_to_owners'; const CAN_ASSIGN_TO_OTHER = 'can_assign_to_other'; @@ -125,6 +126,7 @@ self::CAN_MANAGE_MILESTONES, self::CAN_UPLOAD_FILES, self::CAN_MANAGE_FILES, + self::CAN_MANAGE_TICKETS, self::CAN_ASSIGN_TO_OWNERS, self::CAN_ASSIGN_TO_OTHER, ); // array @@ -143,6 +145,7 @@ ProjectUsers::CAN_MANAGE_MILESTONES => lang('can manage milestones'), ProjectUsers::CAN_UPLOAD_FILES => lang('can upload files'), ProjectUsers::CAN_MANAGE_FILES => lang('can manage files'), + ProjectUsers::CAN_MANAGE_TICKETS => lang('can manage tickets'), ProjectUsers::CAN_ASSIGN_TO_OWNERS => lang('can assign to owners'), ProjectUsers::CAN_ASSIGN_TO_OTHER => lang('can assign to other'), ); // array diff -urN projectpier-0.8.0.3/application/models/project_users/base/BaseProjectUser.class.php projectpier-0.8.0.3-trackerpatch/application/models/project_users/base/BaseProjectUser.class.php --- projectpier-0.8.0.3/application/models/project_users/base/BaseProjectUser.class.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/application/models/project_users/base/BaseProjectUser.class.php Wed Aug 6 22:21:15 2008 @@ -210,6 +210,28 @@ } // setCanManageFiles() /** + * Return value of 'can_manage_tickets' field + * + * @access public + * @param void + * @return boolean + */ + function getCanManageTickets() { + return $this->getColumnValue('can_manage_tickets'); + } // getCanManageFiles() + + /** + * Set value of 'can_manage_tickets' field + * + * @access public + * @param boolean $value + * @return boolean + */ + function setCanManageTickets($value) { + return $this->setColumnValue('can_manage_tickets', $value); + } // setCanManageFiles() + + /** * Return value of 'can_assign_to_owners' field * * @access public diff -urN projectpier-0.8.0.3/application/models/project_users/base/BaseProjectUsers.class.php projectpier-0.8.0.3-trackerpatch/application/models/project_users/base/BaseProjectUsers.class.php --- projectpier-0.8.0.3/application/models/project_users/base/BaseProjectUsers.class.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/application/models/project_users/base/BaseProjectUsers.class.php Wed Aug 6 22:26:37 2008 @@ -14,7 +14,7 @@ * @var array * @static */ - static private $columns = array('project_id' => DATA_TYPE_INTEGER, 'user_id' => DATA_TYPE_INTEGER, 'created_on' => DATA_TYPE_DATETIME, 'created_by_id' => DATA_TYPE_INTEGER, 'can_manage_messages' => DATA_TYPE_BOOLEAN, 'can_manage_tasks' => DATA_TYPE_BOOLEAN, 'can_manage_milestones' => DATA_TYPE_BOOLEAN, 'can_upload_files' => DATA_TYPE_BOOLEAN, 'can_manage_files' => DATA_TYPE_BOOLEAN, 'can_assign_to_owners' => DATA_TYPE_BOOLEAN, 'can_assign_to_other' => DATA_TYPE_BOOLEAN); + static private $columns = array('project_id' => DATA_TYPE_INTEGER, 'user_id' => DATA_TYPE_INTEGER, 'created_on' => DATA_TYPE_DATETIME, 'created_by_id' => DATA_TYPE_INTEGER, 'can_manage_messages' => DATA_TYPE_BOOLEAN, 'can_manage_tasks' => DATA_TYPE_BOOLEAN, 'can_manage_milestones' => DATA_TYPE_BOOLEAN, 'can_upload_files' => DATA_TYPE_BOOLEAN, 'can_manage_files' => DATA_TYPE_BOOLEAN, 'can_manage_tickets' => DATA_TYPE_BOOLEAN, 'can_assign_to_owners' => DATA_TYPE_BOOLEAN, 'can_assign_to_other' => DATA_TYPE_BOOLEAN); /** * Construct diff -urN projectpier-0.8.0.3/application/models/projects/Project.class.php projectpier-0.8.0.3-trackerpatch/application/models/projects/Project.class.php --- projectpier-0.8.0.3/application/models/projects/Project.class.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/application/models/projects/Project.class.php Wed Aug 6 22:34:21 2008 @@ -165,6 +165,62 @@ private $completed_task_lists; // --------------------------------------------------- + // Trac + // --------------------------------------------------- + + /** + * All categories in this project + * + * @var array + */ + private $categories; + + /** + * All tickets in this project + * + * @var array + */ + private $all_tickets; + + /** + * Array of all tickets. If user is not member of owner company private tickets + * will be excluded from the list + * + * @var array + */ + private $tickets; + + /** + * All open tickets in this project + * + * @var array + */ + private $all_open_tickets; + + /** + * Array of open tickets. If user is not member of owner company private tickets + * will be excluded from the list + * + * @var array + */ + private $open_tickets; + + /** + * All closed tickets in this project + * + * @var array + */ + private $all_closed_tickets; + + /** + * Array of closed tickets. If user is not member of owner company private tickets + * will be excluded from the list + * + * @var array + */ + private $closed_tickets; + + // --------------------------------------------------- // Tags // --------------------------------------------------- @@ -609,6 +665,119 @@ } // getCompletedTaskLists // --------------------------------------------------- + // Trac + // --------------------------------------------------- + + /** + * Return all categories + * + * @param void + * @return array + */ + function getCategories() { + if(is_null($this->categories)) { + $this->categories = Categories::getProjectCategories($this); + } // if + return $this->categories; + } // getCategories + + /** + * This function will return all tickets in project and it will not exclude private + * tickets if logged user is not member of owner company + * + * @param void + * @return array + */ + function getAllTickets() { + if(is_null($this->all_tickets)) { + $this->all_tickets = ProjectTickets::getProjectTickets($this, true); + } // if + return $this->all_tickets; + } // getAllTickets + + /** + * Return only the tickets that current user can see (if not member of owner company private + * tickets will be excluded) + * + * @param void + * @return array + */ + function getTickets() { + if(logged_user()->isMemberOfOwnerCompany()) { + return $this->getAllTickets(); // members of owner company can view all tickets + } // if + + if(is_null($this->tickets)) { + $this->tickets = ProjectTickets::getProjectTickets($this, false); + } // if + return $this->tickets; + } // getTickets + + /** + * This function will return all open tickets in project and it will not exclude private + * tickets if logged user is not member of owner company + * + * @param void + * @return array + */ + function getAllOpenTickets() { + if(is_null($this->all_open_tickets)) { + $this->all_open_tickets = ProjectTickets::getOpenProjectTickets($this, true); + } // if + return $this->all_open_tickets; + } // getAllOpenTickets + + /** + * Return only the open tickets that current user can see (if not member of owner company private + * tickets will be excluded) + * + * @param void + * @return array + */ + function getOpenTickets() { + if(logged_user()->isMemberOfOwnerCompany()) { + return $this->getAllOpenTickets(); // members of owner company can view all tickets + } // if + + if(is_null($this->open_tickets)) { + $this->open_tickets = ProjectTickets::getOpenProjectTickets($this, false); + } // if + return $this->open_tickets; + } // getOpenTickets + + /** + * This function will return all closed tickets in project and it will not exclude private + * tickets if logged user is not member of owner company + * + * @param void + * @return array + */ + function getAllClosedTickets() { + if(is_null($this->all_closed_tickets)) { + $this->all_closed_tickets = ProjectTickets::getClosedProjectTickets($this, true); + } // if + return $this->all_closed_tickets; + } // getAllClosedTickets + + /** + * Return only the closed tickets that current user can see (if not member of owner company private + * tickets will be excluded) + * + * @param void + * @return array + */ + function getClosedTickets() { + if(logged_user()->isMemberOfOwnerCompany()) { + return $this->getAllClosedTickets(); // members of owner company can view all tickets + } // if + + if(is_null($this->closed_tickets)) { + $this->closed_tickets = ProjectTickets::getClosedProjectTickets($this, false); + } // if + return $this->closed_tickets; + } // getClosedTickets + + // --------------------------------------------------- // Tags // --------------------------------------------------- @@ -903,6 +1072,27 @@ } // getUsersTasks // --------------------------------------------------- + // User tickets + // --------------------------------------------------- + + /** + * Return array of task that are assigned to specific user or his company + * + * @param User $user + * @return array + */ + function getUsersTickets(User $user) { + $conditions = DB::prepareString('`project_id` = ? AND ((`assigned_to_user_id` = ? AND `assigned_to_company_id` = ?) OR (`assigned_to_user_id` = ? AND `assigned_to_company_id` = ?) OR (`assigned_to_user_id` = ? AND `assigned_to_company_id` = ?) OR `created_by_id`= ?) AND `closed_on` = ?', array($this->getId(), $user->getId(), $user->getCompanyId(), 0, $user->getCompanyId(), 0, 0, $user->getId(), EMPTY_DATETIME)); + if(!$user->isMemberOfOwnerCompany()) { + $conditions .= DB::prepareString(' AND `is_private` = ?', array(0)); + } // if + return ProjectTickets::findAll(array( + 'conditions' => $conditions, + 'order' => '`created_on`' + )); // findAll + } // getUsersTickets + + // --------------------------------------------------- // Files // --------------------------------------------------- @@ -1129,6 +1319,16 @@ return get_url('milestone', 'index', array('active_project' => $this->getId())); } // getMilestonesUrl + /** + * Return project trac index page URL + * + * @param void + * @return string + */ + function getTracUrl() { + return get_url('trac', 'index', array('active_project' => $this->getId())); + } // getTracUrl + /** * Return project forms index page URL * diff -urN projectpier-0.8.0.3/application/models/ticket_changes/TicketChange.class.php projectpier-0.8.0.3-trackerpatch/application/models/ticket_changes/TicketChange.class.php --- projectpier-0.8.0.3/application/models/ticket_changes/TicketChange.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/models/ticket_changes/TicketChange.class.php Wed Aug 6 22:00:17 2008 @@ -0,0 +1,41 @@ +ticket)) $this->ticket = ProjectTickets::findById($this->getTicketId()); + return $this->ticket; + } // getTicket + + /** + * Return if data needs translation + * + * @param void + * @return ProjectTicket + */ + function dataNeedsTranslation() { + return ($this->getType() == 'priority') || ($this->getType() == 'type') || ($this->getType() == 'status') || ($this->getType() == 'private'); + } // dataNeedsTranslation + + } // TicketChanges + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/models/ticket_changes/TicketChanges.class.php projectpier-0.8.0.3-trackerpatch/application/models/ticket_changes/TicketChanges.class.php --- projectpier-0.8.0.3/application/models/ticket_changes/TicketChanges.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/models/ticket_changes/TicketChanges.class.php Wed Aug 6 22:00:18 2008 @@ -0,0 +1,26 @@ + array('`ticket_id` = ?', $ticket->getId()), + 'order' => '`created_on`' + )); // array + } // getChangesByTicket + + } // TicketChanges + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/models/ticket_changes/base/BaseTicketChange.class.php projectpier-0.8.0.3-trackerpatch/application/models/ticket_changes/base/BaseTicketChange.class.php --- projectpier-0.8.0.3/application/models/ticket_changes/base/BaseTicketChange.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/models/ticket_changes/base/BaseTicketChange.class.php Wed Aug 6 22:00:17 2008 @@ -0,0 +1,161 @@ +getColumnValue('ticket_id'); + } // getTicketId() + + /** + * Set value of 'ticket_id' field + * + * @access public + * @param integer $value + * @return boolean + */ + function setTicketId($value) { + return $this->setColumnValue('ticket_id', $value); + } // setTicketId() + + /** + * Return value of 'type' field + * + * @access public + * @param void + * @return string + */ + function getType() { + return $this->getColumnValue('type'); + } // getType() + + /** + * Set value of 'type' field + * + * @access public + * @param string $value + * @return boolean + */ + function setType($value) { + return $this->setColumnValue('type', $value); + } // setType() + + /** + * Return value of 'from_data' field + * + * @access public + * @param void + * @return string + */ + function getFromData() { + return $this->getColumnValue('from_data'); + } // getFromData() + + /** + * Set value of 'from_data' field + * + * @access public + * @param string $value + * @return boolean + */ + function setFromData($value) { + return $this->setColumnValue('from_data', $value); + } // setFromData() + + /** + * Return value of 'to_data' field + * + * @access public + * @param void + * @return string + */ + function getToData() { + return $this->getColumnValue('to_data'); + } // getToData() + + /** + * Set value of 'to_data' field + * + * @access public + * @param string $value + * @return boolean + */ + function setToData($value) { + return $this->setColumnValue('to_data', $value); + } // setToData() + + /** + * Return value of 'created_on' field + * + * @access public + * @param void + * @return DateTimeValue + */ + function getCreatedOn() { + return $this->getColumnValue('created_on'); + } // getCreatedOn() + + /** + * Set value of 'created_on' field + * + * @access public + * @param DateTimeValue $value + * @return boolean + */ + function setCreatedOn($value) { + return $this->setColumnValue('created_on', $value); + } // setCreatedOn() + + /** + * Return value of 'created_by_id' field + * + * @access public + * @param void + * @return integer + */ + function getCreatedById() { + return $this->getColumnValue('created_by_id'); + } // getCreatedById() + + /** + * Set value of 'created_by_id' field + * + * @access public + * @param integer $value + * @return boolean + */ + function setCreatedById($value) { + return $this->setColumnValue('created_by_id', $value); + } // setCreatedById() + + + /** + * Return manager instance + * + * @access protected + * @param void + * @return TicketChanges + */ + function manager() { + if(!($this->manager instanceof TicketChanges)) $this->manager = TicketChanges::instance(); + return $this->manager; + } // manager + + } // BaseTicketChange + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/models/ticket_changes/base/BaseTicketChanges.class.php projectpier-0.8.0.3-trackerpatch/application/models/ticket_changes/base/BaseTicketChanges.class.php --- projectpier-0.8.0.3/application/models/ticket_changes/base/BaseTicketChanges.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/models/ticket_changes/base/BaseTicketChanges.class.php Wed Aug 6 22:00:17 2008 @@ -0,0 +1,220 @@ + Column type map + * + * @var array + * @static + */ + static private $columns = array('id' => DATA_TYPE_INTEGER, 'ticket_id' => DATA_TYPE_INTEGER, 'type' => DATA_TYPE_STRING, 'from_data' => DATA_TYPE_STRING, 'to_data' => DATA_TYPE_STRING, 'created_on' => DATA_TYPE_DATETIME, 'created_by_id' => DATA_TYPE_INTEGER); + + /** + * Construct + * + * @return BaseTicketChanges + */ + function __construct() { + parent::__construct('TicketChange', 'ticket_changes', true); + } // __construct + + // ------------------------------------------------------- + // Description methods + // ------------------------------------------------------- + + /** + * Return array of object columns + * + * @access public + * @param void + * @return array + */ + function getColumns() { + return array_keys(self::$columns); + } // getColumns + + /** + * Return column type + * + * @access public + * @param string $column_name + * @return string + */ + function getColumnType($column_name) { + if(isset(self::$columns[$column_name])) { + return self::$columns[$column_name]; + } else { + return DATA_TYPE_STRING; + } // if + } // getColumnType + + /** + * Return array of PK columns. If only one column is PK returns its name as string + * + * @access public + * @param void + * @return array or string + */ + function getPkColumns() { + return 'id'; + } // getPkColumns + + /** + * Return name of first auto_incremenent column if it exists + * + * @access public + * @param void + * @return string + */ + function getAutoIncrementColumn() { + return 'id'; + } // getAutoIncrementColumn + + // ------------------------------------------------------- + // Finders + // ------------------------------------------------------- + + /** + * Do a SELECT query over database with specified arguments + * + * @access public + * @param array $arguments Array of query arguments. Fields: + * + * - one - select first row + * - conditions - additional conditions + * - order - order by string + * - offset - limit offset, valid only if limit is present + * - limit + * + * @return one or TicketChanges objects + * @throws DBQueryError + */ + function find($arguments = null) { + if(isset($this) && instance_of($this, 'TicketChanges')) { + return parent::find($arguments); + } else { + return TicketChanges::instance()->find($arguments); + } // if + } // find + + /** + * Find all records + * + * @access public + * @param array $arguments + * @return one or TicketChanges objects + */ + function findAll($arguments = null) { + if(isset($this) && instance_of($this, 'TicketChanges')) { + return parent::findAll($arguments); + } else { + return TicketChanges::instance()->findAll($arguments); + } // if + } // findAll + + /** + * Find one specific record + * + * @access public + * @param array $arguments + * @return TicketChange + */ + function findOne($arguments = null) { + if(isset($this) && instance_of($this, 'TicketChanges')) { + return parent::findOne($arguments); + } else { + return TicketChanges::instance()->findOne($arguments); + } // if + } // findOne + + /** + * Return object by its PK value + * + * @access public + * @param mixed $id + * @param boolean $force_reload If true cache will be skipped and data will be loaded from database + * @return TicketChange + */ + function findById($id, $force_reload = false) { + if(isset($this) && instance_of($this, 'TicketChanges')) { + return parent::findById($id, $force_reload); + } else { + return TicketChanges::instance()->findById($id, $force_reload); + } // if + } // findById + + /** + * Return number of rows in this table + * + * @access public + * @param string $conditions Query conditions + * @return integer + */ + function count($condition = null) { + if(isset($this) && instance_of($this, 'TicketChanges')) { + return parent::count($condition); + } else { + return TicketChanges::instance()->count($condition); + } // if + } // count + + /** + * Delete rows that match specific conditions. If $conditions is NULL all rows from table will be deleted + * + * @access public + * @param string $conditions Query conditions + * @return boolean + */ + function delete($condition = null) { + if(isset($this) && instance_of($this, 'TicketChanges')) { + return parent::delete($condition); + } else { + return TicketChanges::instance()->delete($condition); + } // if + } // delete + + /** + * This function will return paginated result. Result is an array where first element is + * array of returned object and second populated pagination object that can be used for + * obtaining and rendering pagination data using various helpers. + * + * Items and pagination array vars are indexed with 0 for items and 1 for pagination + * because you can't use associative indexing with list() construct + * + * @access public + * @param array $arguments Query argumens (@see find()) Limit and offset are ignored! + * @param integer $items_per_page Number of items per page + * @param integer $current_page Current page number + * @return array + */ + function paginate($arguments = null, $items_per_page = 10, $current_page = 1) { + if(isset($this) && instance_of($this, 'TicketChanges')) { + return parent::paginate($arguments, $items_per_page, $current_page); + } else { + return TicketChanges::instance()->paginate($arguments, $items_per_page, $current_page); + } // if + } // paginate + + /** + * Return manager instance + * + * @return TicketChanges + */ + function instance() { + static $instance; + if(!instance_of($instance, 'TicketChanges')) { + $instance = new TicketChanges(); + } // if + return $instance; + } // instance + + } // TicketChanges + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/models/ticket_subscriptions/TicketSubscription.class.php projectpier-0.8.0.3-trackerpatch/application/models/ticket_subscriptions/TicketSubscription.class.php --- projectpier-0.8.0.3/application/models/ticket_subscriptions/TicketSubscription.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/models/ticket_subscriptions/TicketSubscription.class.php Wed Aug 6 22:00:18 2008 @@ -0,0 +1,49 @@ +user)) $this->user = Users::findById($this->getUserId()); + return $this->user; + } // getUser + + /** + * Return ticket object + * + * @param void + * @return ProjectTicket + */ + function getTicket() { + if(is_null($this->ticket)) $this->ticket = ProjectTickets::findById($this->getTicketId()); + return $this->ticket; + } // getTicket + + } // TicketSubscription + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/models/ticket_subscriptions/TicketSubscriptions.class.php projectpier-0.8.0.3-trackerpatch/application/models/ticket_subscriptions/TicketSubscriptions.class.php --- projectpier-0.8.0.3/application/models/ticket_subscriptions/TicketSubscriptions.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/models/ticket_subscriptions/TicketSubscriptions.class.php Wed Aug 6 22:00:18 2008 @@ -0,0 +1,73 @@ + '`ticket_id` = ' . DB::escape($ticket->getId()) + )); // findAll + if(is_array($subscriptions)) { + foreach($subscriptions as $subscription) { + $user = $subscription->getUser(); + if($user instanceof User) $users[] = $user; + } // foreach + } // if + return count($users) ? $users : null; + } // getUsersByTicket + + /** + * Return array of tickets that $user is subscribed to + * + * @param User $user + * @return array + */ + static function getTicketsByUser(User $user) { + $tickets = array(); + $subscriptions = TicketSubscriptions::findAll(array( + 'conditions' => '`user_id` = ' . DB::escape($user->getId()) + )); // findAll + if(is_array($subscriptions)) { + foreach($subscriptions as $subscription) { + $ticket = $subscription->getTicket(); + if($tickets instanceof ProjectTicket) $tickets[] = $ticket; + } // foreach + } // if + return count($tickets) ? $tickets : null; + } // getTicketsByUser + + /** + * Clear subscriptions by ticket + * + * @param ProjectTicket $ticket + * @return boolean + */ + static function clearByTicket(ProjectTicket $ticket) { + return TicketSubscriptions::delete('`ticket_id` = ' . DB::escape($ticket->getId())); + } // clearByTicket + + /** + * Clear subscriptions by user + * + * @param User $user + * @return boolean + */ + static function clearByUser(User $user) { + return TicketSubscriptions::delete('`user_id` = ' . DB::escape($user->getId())); + } // clearByUser + + } // TicketSubscriptions + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/models/ticket_subscriptions/base/BaseTicketSubscription.class.php projectpier-0.8.0.3-trackerpatch/application/models/ticket_subscriptions/base/BaseTicketSubscription.class.php --- projectpier-0.8.0.3/application/models/ticket_subscriptions/base/BaseTicketSubscription.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/models/ticket_subscriptions/base/BaseTicketSubscription.class.php Wed Aug 6 22:00:18 2008 @@ -0,0 +1,73 @@ +getColumnValue('ticket_id'); + } // getTicketId() + + /** + * Set value of 'ticket_id' field + * + * @access public + * @param integer $value + * @return boolean + */ + function setTicketId($value) { + return $this->setColumnValue('ticket_id', $value); + } // setTicketId() + + /** + * Return value of 'user_id' field + * + * @access public + * @param void + * @return integer + */ + function getUserId() { + return $this->getColumnValue('user_id'); + } // getUserId() + + /** + * Set value of 'user_id' field + * + * @access public + * @param integer $value + * @return boolean + */ + function setUserId($value) { + return $this->setColumnValue('user_id', $value); + } // setUserId() + + + /** + * Return manager instance + * + * @access protected + * @param void + * @return TicketSubscriptions + */ + function manager() { + if(!($this->manager instanceof TicketSubscriptions)) $this->manager = TicketSubscriptions::instance(); + return $this->manager; + } // manager + + } // BaseTicketSubscription + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/models/ticket_subscriptions/base/BaseTicketSubscriptions.class.php projectpier-0.8.0.3-trackerpatch/application/models/ticket_subscriptions/base/BaseTicketSubscriptions.class.php --- projectpier-0.8.0.3/application/models/ticket_subscriptions/base/BaseTicketSubscriptions.class.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/models/ticket_subscriptions/base/BaseTicketSubscriptions.class.php Wed Aug 6 22:00:18 2008 @@ -0,0 +1,223 @@ + Column type map + * + * @var array + * @static + */ + static private $columns = array('ticket_id' => DATA_TYPE_INTEGER, 'user_id' => DATA_TYPE_INTEGER); + + /** + * Construct + * + * @return BaseTicketSubscriptions + */ + function __construct() { + parent::__construct('TicketSubscription', 'ticket_subscriptions', true); + } // __construct + + // ------------------------------------------------------- + // Description methods + // ------------------------------------------------------- + + /** + * Return array of object columns + * + * @access public + * @param void + * @return array + */ + function getColumns() { + return array_keys(self::$columns); + } // getColumns + + /** + * Return column type + * + * @access public + * @param string $column_name + * @return string + */ + function getColumnType($column_name) { + if(isset(self::$columns[$column_name])) { + return self::$columns[$column_name]; + } else { + return DATA_TYPE_STRING; + } // if + } // getColumnType + + /** + * Return array of PK columns. If only one column is PK returns its name as string + * + * @access public + * @param void + * @return array or string + */ + function getPkColumns() { + return array ( + 0 => 'ticket_id', + 1 => 'user_id', +); + } // getPkColumns + + /** + * Return name of first auto_incremenent column if it exists + * + * @access public + * @param void + * @return string + */ + function getAutoIncrementColumn() { + return NULL; + } // getAutoIncrementColumn + + // ------------------------------------------------------- + // Finders + // ------------------------------------------------------- + + /** + * Do a SELECT query over database with specified arguments + * + * @access public + * @param array $arguments Array of query arguments. Fields: + * + * - one - select first row + * - conditions - additional conditions + * - order - order by string + * - offset - limit offset, valid only if limit is present + * - limit + * + * @return one or TicketSubscriptions objects + * @throws DBQueryError + */ + function find($arguments = null) { + if(isset($this) && instance_of($this, 'TicketSubscriptions')) { + return parent::find($arguments); + } else { + return TicketSubscriptions::instance()->find($arguments); + } // if + } // find + + /** + * Find all records + * + * @access public + * @param array $arguments + * @return one or TicketSubscriptions objects + */ + function findAll($arguments = null) { + if(isset($this) && instance_of($this, 'TicketSubscriptions')) { + return parent::findAll($arguments); + } else { + return TicketSubscriptions::instance()->findAll($arguments); + } // if + } // findAll + + /** + * Find one specific record + * + * @access public + * @param array $arguments + * @return TicketSubscription + */ + function findOne($arguments = null) { + if(isset($this) && instance_of($this, 'TicketSubscriptions')) { + return parent::findOne($arguments); + } else { + return TicketSubscriptions::instance()->findOne($arguments); + } // if + } // findOne + + /** + * Return object by its PK value + * + * @access public + * @param mixed $id + * @param boolean $force_reload If true cache will be skipped and data will be loaded from database + * @return TicketSubscription + */ + function findById($id, $force_reload = false) { + if(isset($this) && instance_of($this, 'TicketSubscriptions')) { + return parent::findById($id, $force_reload); + } else { + return TicketSubscriptions::instance()->findById($id, $force_reload); + } // if + } // findById + + /** + * Return number of rows in this table + * + * @access public + * @param string $conditions Query conditions + * @return integer + */ + function count($condition = null) { + if(isset($this) && instance_of($this, 'TicketSubscriptions')) { + return parent::count($condition); + } else { + return TicketSubscriptions::instance()->count($condition); + } // if + } // count + + /** + * Delete rows that match specific conditions. If $conditions is NULL all rows from table will be deleted + * + * @access public + * @param string $conditions Query conditions + * @return boolean + */ + function delete($condition = null) { + if(isset($this) && instance_of($this, 'TicketSubscriptions')) { + return parent::delete($condition); + } else { + return TicketSubscriptions::instance()->delete($condition); + } // if + } // delete + + /** + * This function will return paginated result. Result is an array where first element is + * array of returned object and second populated pagination object that can be used for + * obtaining and rendering pagination data using various helpers. + * + * Items and pagination array vars are indexed with 0 for items and 1 for pagination + * because you can't use associative indexing with list() construct + * + * @access public + * @param array $arguments Query argumens (@see find()) Limit and offset are ignored! + * @param integer $items_per_page Number of items per page + * @param integer $current_page Current page number + * @return array + */ + function paginate($arguments = null, $items_per_page = 10, $current_page = 1) { + if(isset($this) && instance_of($this, 'TicketSubscriptions')) { + return parent::paginate($arguments, $items_per_page, $current_page); + } else { + return TicketSubscriptions::instance()->paginate($arguments, $items_per_page, $current_page); + } // if + } // paginate + + /** + * Return manager instance + * + * @return TicketSubscriptions + */ + function instance() { + static $instance; + if(!instance_of($instance, 'TicketSubscriptions')) { + $instance = new TicketSubscriptions(); + } // if + return $instance; + } // instance + + } // TicketSubscriptions + +?> \ No newline at end of file diff -urN projectpier-0.8.0.3/application/views/application/user_box.php projectpier-0.8.0.3-trackerpatch/application/views/application/user_box.php --- projectpier-0.8.0.3/application/views/application/user_box.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/application/views/application/user_box.php Wed Aug 6 22:00:18 2008 @@ -21,6 +21,7 @@ +
+ + + + +
+ + 'ticketFormSummary', 'class' => 'title')) ?> +
+ +
+ + 'ticketFormType')) ?> +
+ +
+ + getProject(), array_var($ticket_data, 'category_id'), array('id' => 'ticketFormCategory')) ?> +
+ +
+ + 'ticketFormPriority')) ?> +
+ +
+ + 'ticketFormAssignedTo')) ?> +
+ +
+
+ + 'messageFormDescription')) ?> +
+ +isMemberOfOwnerCompany()) { ?> +
+ + +
+
+
+
+
+
+ + +
+ +

+getCompanies() as $company) { ?> + +getUsersOnProject(active_project())) && count($users)) { ?> +
+
getId() . ']', array_var($ticket_data, 'notify_company_' . $company->getId()), array('id' => 'notifyCompany' . $company->getId(), 'onclick' => 'App.modules.addMessageForm.emailNotifyClickCompany(' . $company->getId() . ')')) ?>
+
+
    + +
  • getId() . ']', array_var($message_data, 'notify_user_' . $user->getId()), array('id' => 'notifyUser' . $user->getId(), 'onclick' => 'App.modules.addMessageForm.emailNotifyClickUser(' . $company->getId() . ', ' . $user->getId() . ')')) ?>
  • + + +
+
+
+ + +
+ +canAttachFile(logged_user(), active_project())) { ?> + + + + isNew() ? lang('add tracTicket') : lang('edit tracTicket')) ?> +
\ No newline at end of file diff -urN projectpier-0.8.0.3/application/views/trac/add_category.php projectpier-0.8.0.3-trackerpatch/application/views/trac/add_category.php --- projectpier-0.8.0.3/application/views/trac/add_category.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/views/trac/add_category.php Wed Aug 6 22:00:18 2008 @@ -0,0 +1,47 @@ +isNew() ? lang('add tracCategory') : lang('edit tracCategory')); + project_tabbed_navigation(PROJECT_TAB_TICKETS); + project_crumbs(array( + array(lang('tickets'), get_url('trac')), + array(lang('tracCategories'), get_url('trac','categories')), + array($category->isNew() ? lang('add tracCategory') : lang('edit tracCategory')) + )); + $canEdit = $category->isNew() || $category->canEdit(logged_user()); + add_stylesheet_to_page('project/tickets.css'); +?> + +isNew()) { ?> +
+ + + + + + +
+ + + 'categoryFormName', 'class' => 'title')) ?> + +
getName()) ?>
+ +
+ +
+ + + 'categoryFormDescription', 'class' => 'short')) ?> + +
getDescription()) ?>
+ +
+ + + isNew() ? lang('add tracCategory') : lang('edit tracCategory')) ?> + +isNew() && $category->canDelete(logged_user())) { ?> + + +
\ No newline at end of file diff -urN projectpier-0.8.0.3/application/views/trac/categories.php projectpier-0.8.0.3-trackerpatch/application/views/trac/categories.php --- projectpier-0.8.0.3/application/views/trac/categories.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/views/trac/categories.php Wed Aug 6 22:00:18 2008 @@ -0,0 +1,32 @@ + + +
+ + + + + + + + + + + +
CategoryDescription
getName() ?>getShortDescription() ?>
+
+ +

+ \ No newline at end of file diff -urN projectpier-0.8.0.3/application/views/trac/edit.php projectpier-0.8.0.3-trackerpatch/application/views/trac/edit.php --- projectpier-0.8.0.3/application/views/trac/edit.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/views/trac/edit.php Wed Aug 6 22:00:18 2008 @@ -0,0 +1,148 @@ +canEdit(logged_user()); + + // Set page title and set crumbs to index + $title = $canEdit ? 'edit tracTicket' : 'view tracTicket'; + set_page_title(lang($title)); + project_tabbed_navigation(PROJECT_TAB_TICKETS); + $crumbs = array(array(lang('tickets'), get_url('trac'))); + if ($ticket->isClosed()) { + $crumbs[] = array(lang('closed tracTickets'), ProjectTickets::getIndexUrl(true)); + } + $crumbs[] = array(lang($title)); + project_crumbs($crumbs); + + if ($ticket->canChangeStatus(logged_user())) { + if ($ticket->isClosed()) { + add_page_action(lang('open tracTicket'), $ticket->getOpenUrl()); + } else { + add_page_action(lang('close tracTicket'), $ticket->getCloseUrl()); + } + } + add_stylesheet_to_page('project/tickets.css'); +?> +isPrivate()) { ?> +
+ +

getId()); ?>

+

: getStatus()); ?>

+ +
+ + + + +
+ + +
+ + 'ticketFormSummary', 'class' => 'title')) ?> +
+
+ +

getSummary()); ?>

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
:getCreatedByDisplayName(); ?>: +getUpdated()) { ?> + getUpdatedOn()), $ticket->getUpdatedBy()->getCardUrl(), $ticket->getUpdatedByDisplayName(), lang($ticket->getUpdated())); ?> + +
'ticketFormAssignedTo')) ?> +getAssignedTo()) { ?> + getAssignedTo()->getObjectName()) ?> + + 'ticketFormPriority')) ?>getPriority()); ?>
'ticketFormType')) ?>getType()); ?>getProject(), array_var($ticket_data, 'category_id'), array('id' => 'ticketFormCategory')) ?> +getCategory()) { ?> + getCategory()->getName()) ?> + +
+ +
+
+ : +
getDescription()); ?>
+
+
+ + + isNew() ? lang('add tracTicket') : lang('edit tracTicket')) ?> + +
+
+
+ canEdit(logged_user())) ?> +
+ +
getViewUrl()) ?>
+ +

+ +
+ + + + + + + + + + + +dataNeedsTranslation()) { ?> + + + + + + + + + + +
getType()) ?>getFromData()) ?>getToData()) ?>getFromData() ?>getToData() ?>getCreatedByDisplayName() ?>getCreatedOn()) ?>
+
+ +

+ \ No newline at end of file diff -urN projectpier-0.8.0.3/application/views/trac/edit_sidebar.php projectpier-0.8.0.3-trackerpatch/application/views/trac/edit_sidebar.php --- projectpier-0.8.0.3/application/views/trac/edit_sidebar.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/views/trac/edit_sidebar.php Wed Aug 6 22:00:18 2008 @@ -0,0 +1,44 @@ +
+

+
+

+ + + +

+ +isSubscriber(logged_user())) { ?> +

+ +
+
+ +canUpdateOptions(logged_user())) { ?> +
+

+
+
+
+
+ + isPrivate(), lang('yes'), lang('no')) ?> +
+
+ +
+
+
+canDelete(logged_user())) { ?> + + +
+
+ \ No newline at end of file diff -urN projectpier-0.8.0.3/application/views/trac/index.php projectpier-0.8.0.3-trackerpatch/application/views/trac/index.php --- projectpier-0.8.0.3/application/views/trac/index.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/views/trac/index.php Wed Aug 6 22:00:18 2008 @@ -0,0 +1,30 @@ + '#PAGE#'); + if ($closed) $options_pagination['closed'] = true; +?> + +
+
+assign('tickets', $tickets); + $this->includeTemplate(get_template_path('view_tickets', 'trac')); +?> +
+
+ +

+ \ No newline at end of file diff -urN projectpier-0.8.0.3/application/views/trac/index_sidebar.php projectpier-0.8.0.3-trackerpatch/application/views/trac/index_sidebar.php --- projectpier-0.8.0.3/application/views/trac/index_sidebar.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/views/trac/index_sidebar.php Wed Aug 6 22:00:19 2008 @@ -0,0 +1,10 @@ +includeTemplate(get_template_path('trac_sidebar', 'trac')) ?> +
+

+ +
\ No newline at end of file diff -urN projectpier-0.8.0.3/application/views/trac/trac_sidebar.php projectpier-0.8.0.3-trackerpatch/application/views/trac/trac_sidebar.php --- projectpier-0.8.0.3/application/views/trac/trac_sidebar.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/views/trac/trac_sidebar.php Wed Aug 6 22:00:19 2008 @@ -0,0 +1,10 @@ +
+

+
+ +
+
\ No newline at end of file diff -urN projectpier-0.8.0.3/application/views/trac/view_tickets.php projectpier-0.8.0.3-trackerpatch/application/views/trac/view_tickets.php --- projectpier-0.8.0.3/application/views/trac/view_tickets.php Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/application/views/trac/view_tickets.php Wed Aug 6 22:00:19 2008 @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + +
getId() ?>getSummary() ?>getType()) ?> +getCategory()) { ?> + getCategory()->getName()) ?> + + getCreatedBy()->getDisplayName() ?> +getAssignedTo()) { ?> + getAssignedTo()->getObjectName()) ?> + +
\ No newline at end of file diff -urN projectpier-0.8.0.3/language/en_us/actions.php projectpier-0.8.0.3-trackerpatch/language/en_us/actions.php --- projectpier-0.8.0.3/language/en_us/actions.php Sun Feb 17 15:12:58 2008 +++ projectpier-0.8.0.3-trackerpatch/language/en_us/actions.php Wed Aug 6 22:00:19 2008 @@ -59,6 +59,20 @@ 'mark task as completed' => 'Mark task as completed', 'mark task as open' => 'Mark task as open', + // Bug Trac + 'open tracTickets' => 'Open tickets', + 'closed tracTickets' => 'Closed tickets', + 'add tracTicket' => 'Add ticket', + 'edit tracTicket' => 'Edit ticket', + 'view tracTicket' => 'View ticket', + 'open tracTicket' => 'Open ticket', + 'close tracTicket' => 'Close ticket', + 'delete tracTicket' => 'Delete ticket', + 'add tracCategory' => 'Add category', + 'edit tracCategory' => 'Edit category', + 'tracCategories' => 'Trac categories', + 'update ticket options' => 'Update options', + // Milestone 'add milestone' => 'Add milestone', 'edit milestone' => 'Edit milestone', diff -urN projectpier-0.8.0.3/language/en_us/emails.php projectpier-0.8.0.3-trackerpatch/language/en_us/emails.php --- projectpier-0.8.0.3/language/en_us/emails.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/language/en_us/emails.php Wed Aug 6 22:00:19 2008 @@ -5,6 +5,7 @@ // Titles 'new message' => 'New message', 'new comment' => 'New comment', + 'new ticket' => 'New ticket', 'your account created' => 'Your account has been created', 'your password' => 'Your password', 'milestone assigned to you' => 'Milestone has been assigned to you', @@ -20,6 +21,13 @@ 'new comment posted' => 'New comment on "%s" has been posted', 'view new comment' => 'View that comment', + + 'new ticket posted' => 'New ticket "%s" has been posted in "%s" project', + 'ticket edited' => 'Ticket "%s" has been edited in "%s" project', + 'ticket closed' => 'Ticket "%s" has been closed in "%s" project', + 'ticket opened' => 'Ticket "%s" has been opened in "%s" project', + 'attached files to ticket' => 'Some files have been attached to ticket "%s" in "%s" project', + 'view new ticket' => 'View that ticket', 'user created your account' => '%s created new account for you', 'visit and login' => 'Visit %s and login with', diff -urN projectpier-0.8.0.3/language/en_us/errors.php projectpier-0.8.0.3-trackerpatch/language/en_us/errors.php --- projectpier-0.8.0.3/language/en_us/errors.php Sun Feb 10 12:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/language/en_us/errors.php Wed Aug 6 22:00:19 2008 @@ -69,6 +69,13 @@ // Add task 'task text required' => 'Task text is required', + // Add category + 'category name required' => 'Category name value is required', + + // Add ticket + 'ticket summary required' => 'Summary value is required', + 'ticket description required' => 'Description value is required', + // Add project form 'form name required' => 'Form name is required', 'form name unique' => 'Form name must be unique', diff -urN projectpier-0.8.0.3/language/en_us/general.php projectpier-0.8.0.3-trackerpatch/language/en_us/general.php --- projectpier-0.8.0.3/language/en_us/general.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/language/en_us/general.php Wed Aug 6 22:00:19 2008 @@ -101,6 +101,7 @@ 'changelog' => 'Changelog', 'hint' => 'Hint', 'order' => 'Order', + 'private' => 'Private', 'project calendar' => '%s calendar', 'user calendar' => '%s\'s calendar', diff -urN projectpier-0.8.0.3/language/en_us/messages.php projectpier-0.8.0.3-trackerpatch/language/en_us/messages.php --- projectpier-0.8.0.3/language/en_us/messages.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/language/en_us/messages.php Wed Aug 6 22:30:29 2008 @@ -16,6 +16,7 @@ 'no comments associated with object' => 'There are no comments posted for this object', 'no messages in project' => 'There are no messages in this project', 'no subscribers' => 'There are no users subscribed to this message', + 'no ticket subscribers' => 'There are no users subscribed to this ticket', 'no activities in project' => 'There are no activities logged for this project', 'comment dnx' => 'Requested comment does not exist', 'milestone dnx' => 'Requested milestone does not exist', @@ -67,6 +68,12 @@ 'no files to attach' => 'Please select files that need to be attached', 'no administration tools' => 'There are no registered administration tools in the database', 'administration tool dnx' => 'Administration tool "%s" does not exists', + 'ticket dnx' => 'Requested ticket does not exist', + 'no tickets in project' => 'There are no tickets in this project', + 'no my tickets' => 'There are no tickets assigned to you', + 'no changes in ticket' => 'There are no changes in this ticket', + 'category dnx' => 'Requested category does not exist', + 'no categories in project' => 'There are no categories in this project', 'about to delete' => 'You are about to delete', // Success @@ -99,6 +106,15 @@ 'success open task' => 'Selected task has been reopened', 'success n tasks updated' => '%s tasks updated', + 'success add ticket' => 'Ticket \'%s\' has been added successfully', + 'success edit ticket' => 'Ticket \'%s\' has been updated successfully', + 'success deleted ticket' => 'Ticket \'%s\' and all of its comments has been deleted successfully', + 'success close ticket' => 'Selected ticket has been closed', + 'success open ticket' => 'Selected ticket has been reopened', + 'success add category' => 'Category \'%s\' has been added successfully', + 'success edit category' => 'Category \'%s\' has been updated successfully', + 'success deleted category' => 'Category \'%s\' and all of its comments has been deleted successfully', + 'success add client' => 'Client company %s has been added', 'success edit client' => 'Client company %s has been updated', 'success delete client' => 'Client company %s has been deleted', @@ -127,6 +143,9 @@ 'success subscribe to message' => 'You have been successfully subscribed to this message', 'success unsubscribe to message' => 'You have been successfully unsubscribed from this message', + 'success subscribe to ticket' => 'You have been successfully subscribed to this ticket', + 'success unsubscribe to ticket' => 'You have been successfully unsubscribed from this ticket', + 'success add project form' => 'Form \'%s\' has been added', 'success edit project form' => 'Form \'%s\' has been updated', 'success delete project form' => 'Form \'%s\' has been deleted', @@ -159,10 +178,13 @@ 'error delete owner company' => 'Owner company can\'t be deleted', 'error delete message' => 'Failed to delete selected message', 'error update message options' => 'Failed to update message options', + 'error update ticket options' => 'Failed to update ticket options', 'error delete comment' => 'Failed to delete selected comment', 'error delete milestone' => 'Failed to delete selected milestone', 'error complete task' => 'Failed to complete selected task', 'error open task' => 'Failed to reopen selected task', + 'error close ticket' => 'Failed to close selected ticket', + 'error open ticket' => 'Failed to reopen selected ticket', 'error upload file' => 'Failed to upload file', 'error delete project' => 'Failed to delete selected project', 'error complete project' => 'Failed to complete selected project', @@ -183,6 +205,8 @@ 'error delete company logo' => 'Failed to delete company logo', 'error subscribe to message' => 'Failed to subscribe to selected message', 'error unsubscribe to message' => 'Failed to unsubscribe from selected message', + 'error subscribe to ticket' => 'Failed to subscribe to selected ticket', + 'error unsubscribe to ticket' => 'Failed to unsubscribe from selected ticket', 'error add project form' => 'Failed to add project form', 'error submit project form' => 'Failed to submit project form', 'error delete folder' => 'Failed to delete selected folder', @@ -190,6 +214,8 @@ 'error delete file revision' => 'Failed to delete file revision', 'error delete task list' => 'Failed to delete selected task list', 'error delete task' => 'Failed to delete selected task', + 'error delete ticket' => 'Failed to delete selected ticket', + 'error delete category' => 'Failed to delete selected category', 'error check for upgrade' => 'Failed to check for a new version', 'error attach file' => 'Failed to attach file(s)', 'error detach file' => 'Failed to detach file(s)', @@ -208,6 +234,8 @@ 'confirm delete task list' => 'Are you sure that you want to delete this task lists and all of its tasks?', 'confirm delete task' => 'Are you sure that you want to delete this task?', 'confirm delete comment' => 'Are you sure that you want to delete this comment?', + 'confirm delete ticket' => 'Are you sure that you want to delete this ticket?', + 'confirm delete category' => 'Are you sure that you want to delete this category?', 'confirm delete project' => 'Are you sure that you want to delete this project and all related data (messages, tasks, milestones, files...)?', 'confirm complete project' => 'Are you sure that you want to mark this project as finished? All project actions will be locked', 'confirm open project' => 'Are you sure that you want to mark this project as open? This will unlock all project actions', @@ -222,6 +250,7 @@ 'confirm delete company logo' => 'Are you sure that you want to delete this logo?', 'confirm subscribe' => 'Are you sure that you want to subscribe to this message? You will receive an email everytime someone (except you) posts a comment on this message?', 'confirm unsubscribe' => 'Are you sure that you want to unsubscribe?', + 'confirm subscribe ticket' => 'Are you sure that you want to subscribe to this ticket? You will receive an email everytime someone (except you) makes a change or posts a comment on this ticket', 'confirm delete project form' => 'Are you sure that you want to delete this form?', 'confirm delete folder' => 'Are you sure that you want to delete this folder?', 'confirm delete file' => 'Are you sure that you want to delete this file?', @@ -258,6 +287,15 @@ 'log delete projecttasks' => '\'%s\' deleted', 'log close projecttasks' => '\'%s\' closed', 'log open projecttasks' => '\'%s\' opened', + + 'log add categories' => '\'%s\' added', + 'log edit categories' => '\'%s\' updated', + 'log delete categories' => '\'%s\' deleted', + 'log add projecttickets' => '\'%s\' added', + 'log edit projecttickets' => '\'%s\' updated', + 'log delete projecttickets' => '\'%s\' deleted', + 'log close projecttickets' => '\'%s\' closed', + 'log open projecttickets' => '\'%s\' opened', 'log add projectforms' => '\'%s\' added', 'log edit projectforms' => '\'%s\' updated', diff -urN projectpier-0.8.0.3/language/en_us/objects.php projectpier-0.8.0.3-trackerpatch/language/en_us/objects.php --- projectpier-0.8.0.3/language/en_us/objects.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/language/en_us/objects.php Wed Aug 6 22:00:19 2008 @@ -24,6 +24,8 @@ 'tasks' => 'Tasks', 'task list' => 'Task list', 'task lists' => 'Task lists', + 'ticket' => 'Ticket', + 'tickets' => 'Tickets', 'tag' => 'Tag', 'tags' => 'Tags', 'attachment' => 'Attachment', @@ -73,6 +75,7 @@ 'private task list' => 'Private task lists', 'private comment' => 'Private comment', 'private file' => 'Private file', + 'private ticket' => 'Private ticket', ); // array diff -urN projectpier-0.8.0.3/language/en_us/project_interface.php projectpier-0.8.0.3-trackerpatch/language/en_us/project_interface.php --- projectpier-0.8.0.3/language/en_us/project_interface.php Sun Feb 10 11:45:08 2008 +++ projectpier-0.8.0.3-trackerpatch/language/en_us/project_interface.php Wed Aug 6 22:00:19 2008 @@ -58,6 +58,7 @@ 'collapse additional text' => 'Collapse', 'email notification' => 'Email notification', 'email notification desc' => 'Notify selected people about this message via email', + 'email notification ticket desc' => 'Notify selected people about this ticket via email', 'attach existing file' => 'Attach existing file (from Files section)', 'upload and attach' => 'Upload new file and attach it to message', @@ -65,12 +66,15 @@ 'subscribers desc' => 'Subscribers will receive an email notification whenever someone (except themselves) posts a comment on this message', 'admins can post comments on locked objects desc' => 'Comments are locked, but you as administrator still have the permissions to post comments. Note that if you expect replies from your clients and non-admin users you need to unlock comments for this object (set "Enable comments" option to "Yes").', + 'subscribers ticket desc' => 'Subscribers will receive an email notification whenever someone (except themselves) makes a change or posts a comment on this ticket', + 'all permissions' => 'All', 'can manage messages' => 'Manage messages', 'can manage tasks' => 'Manage tasks', 'can manage milestones' => 'Manage milestones', 'can upload files' => 'Upload files', 'can manage files' => 'Manage files', + 'can manage tickets' => 'Manage tickets', 'can assign to owners' => 'Assign tasks to members of owner company', 'can assign to other' => 'Assign tasks to members of other clients', @@ -117,6 +121,30 @@ 'admin notice comments disabled' => 'Comments are disabled for this object, but you as administrator still can comment. If you expect replies from other, non-admin users you should set value of Enable comments option to Yes.', + // Tickets + 'summary' => 'Summary', + 'category' => 'Category', + 'priority' => 'Priority', + 'assigned to' => 'Assigned to', + 'reported by' => 'Reported by', + 'closed' => 'Closed', + 'open' => 'Open', + 'critical' => 'Critical', + 'major' => 'Major', + 'minor' => 'Minor', + 'trivial' => 'Trivial', + 'defect' => 'Defect', + 'enhancement' => 'Enhancement', + 'feature request' => 'Feature', + 'legend' => 'Legend', + 'ticket #' => 'Ticket #%s', + 'updated on by' => '%s | %s | %s', + 'history' => 'Change history', + 'field' => 'Field', + 'old value' => 'Old value', + 'new value' => 'New value', + 'change date' => 'Change date', + // iCal 'icalendar' => 'iCalendar', 'icalendar subscribe' => 'iCalendar', @@ -141,6 +169,7 @@ 'private task list desc' => 'Private task lists are visible only to owner company members. Members of client companies will not be able to see them.', 'private comment desc' => 'Private comments are visible only to owner company members. Members of client companies will not be able to see them.', 'private file desc' => 'Private files are visible only to the members of the owner company. Members of client companies will not be able to see them', + 'private ticket desc' => 'Private tickets are visible only to owner company members. Members of client companies will not be able to see them.', ); // array diff -urN projectpier-0.8.0.3/language/en_us/site_interface.php projectpier-0.8.0.3-trackerpatch/language/en_us/site_interface.php --- projectpier-0.8.0.3/language/en_us/site_interface.php Tue Jul 22 16:18:54 2008 +++ projectpier-0.8.0.3-trackerpatch/language/en_us/site_interface.php Wed Aug 6 22:00:19 2008 @@ -14,6 +14,9 @@ 'my tasks' => 'My tasks', 'welcome back' => 'Welcome back %s', + // Bug Trac + 'my tickets' => 'My tickets', + 'online users' => 'Online users', 'online users desc' => 'Users who were active in last 15 minutes:', diff -urN projectpier-0.8.0.3/public/assets/themes/acClassic/stylesheets/project/tickets.css projectpier-0.8.0.3-trackerpatch/public/assets/themes/acClassic/stylesheets/project/tickets.css --- projectpier-0.8.0.3/public/assets/themes/acClassic/stylesheets/project/tickets.css Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/public/assets/themes/acClassic/stylesheets/project/tickets.css Wed Aug 6 22:00:31 2008 @@ -0,0 +1,40 @@ +/** Tickets display **/ + +#tickets .critical, #tickets_legend .critical { + background-color: #ffbaba; +} + +#tickets .major, #tickets_legend .major { + background-color: #fcffb1; +} + +#tickets .minor, #tickets_legend .minor { + background-color: #d6e7ff; +} + +#tickets .trivial, #tickets_legend .trivial { + background-color: #ffffff; +} + +#tickets_legend { + width: 120px; +} + +#tickets_legend .legend { + border: 1px solid #000000; + padding: 2px; + margin-bottom: 2px; +} + +/** Ticket display */ +#ticket { + background: #ffd; + border: 1px outset #996; + margin-top: 1em; + padding: .5em 1em; + position: relative; +} + +#ticket table.properties { + width: 100%; +} diff -urN projectpier-0.8.0.3/public/assets/themes/acSimple/stylesheets/project/tickets.css projectpier-0.8.0.3-trackerpatch/public/assets/themes/acSimple/stylesheets/project/tickets.css --- projectpier-0.8.0.3/public/assets/themes/acSimple/stylesheets/project/tickets.css Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/public/assets/themes/acSimple/stylesheets/project/tickets.css Wed Aug 6 22:00:31 2008 @@ -0,0 +1,40 @@ +/** Tickets display **/ + +#tickets .critical, #tickets_legend .critical { + background-color: #ffbaba; +} + +#tickets .major, #tickets_legend .major { + background-color: #fcffb1; +} + +#tickets .minor, #tickets_legend .minor { + background-color: #d6e7ff; +} + +#tickets .trivial, #tickets_legend .trivial { + background-color: #ffffff; +} + +#tickets_legend { + width: 120px; +} + +#tickets_legend .legend { + border: 1px solid #000000; + padding: 2px; + margin-bottom: 2px; +} + +/** Ticket display */ +#ticket { + background: #ffd; + border: 1px outset #996; + margin-top: 1em; + padding: .5em 1em; + position: relative; +} + +#ticket table.properties { + width: 100%; +} diff -urN projectpier-0.8.0.3/public/assets/themes/azul/stylesheets/project/tickets.css projectpier-0.8.0.3-trackerpatch/public/assets/themes/azul/stylesheets/project/tickets.css --- projectpier-0.8.0.3/public/assets/themes/azul/stylesheets/project/tickets.css Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/public/assets/themes/azul/stylesheets/project/tickets.css Wed Aug 6 22:00:31 2008 @@ -0,0 +1,40 @@ +/** Tickets display **/ + +#tickets .critical, #tickets_legend .critical { + background-color: #ffbaba; +} + +#tickets .major, #tickets_legend .major { + background-color: #fcffb1; +} + +#tickets .minor, #tickets_legend .minor { + background-color: #d6e7ff; +} + +#tickets .trivial, #tickets_legend .trivial { + background-color: #ffffff; +} + +#tickets_legend { + width: 120px; +} + +#tickets_legend .legend { + border: 1px solid #000000; + padding: 2px; + margin-bottom: 2px; +} + +/** Ticket display */ +#ticket { + background: #ffd; + border: 1px outset #996; + margin-top: 1em; + padding: .5em 1em; + position: relative; +} + +#ticket table.properties { + width: 100%; +} diff -urN projectpier-0.8.0.3/public/assets/themes/basic-aqua/stylesheets/project/tickets.css projectpier-0.8.0.3-trackerpatch/public/assets/themes/basic-aqua/stylesheets/project/tickets.css --- projectpier-0.8.0.3/public/assets/themes/basic-aqua/stylesheets/project/tickets.css Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/public/assets/themes/basic-aqua/stylesheets/project/tickets.css Wed Aug 6 22:00:31 2008 @@ -0,0 +1,40 @@ +/** Tickets display **/ + +#tickets .critical, #tickets_legend .critical { + background-color: #ffbaba; +} + +#tickets .major, #tickets_legend .major { + background-color: #fcffb1; +} + +#tickets .minor, #tickets_legend .minor { + background-color: #d6e7ff; +} + +#tickets .trivial, #tickets_legend .trivial { + background-color: #ffffff; +} + +#tickets_legend { + width: 120px; +} + +#tickets_legend .legend { + border: 1px solid #000000; + padding: 2px; + margin-bottom: 2px; +} + +/** Ticket display */ +#ticket { + background: #ffd; + border: 1px outset #996; + margin-top: 1em; + padding: .5em 1em; + position: relative; +} + +#ticket table.properties { + width: 100%; +} diff -urN projectpier-0.8.0.3/public/assets/themes/basic-orange/stylesheets/project/tickets.css projectpier-0.8.0.3-trackerpatch/public/assets/themes/basic-orange/stylesheets/project/tickets.css --- projectpier-0.8.0.3/public/assets/themes/basic-orange/stylesheets/project/tickets.css Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/public/assets/themes/basic-orange/stylesheets/project/tickets.css Wed Aug 6 22:00:31 2008 @@ -0,0 +1,40 @@ +/** Tickets display **/ + +#tickets .critical, #tickets_legend .critical { + background-color: #ffbaba; +} + +#tickets .major, #tickets_legend .major { + background-color: #fcffb1; +} + +#tickets .minor, #tickets_legend .minor { + background-color: #d6e7ff; +} + +#tickets .trivial, #tickets_legend .trivial { + background-color: #ffffff; +} + +#tickets_legend { + width: 120px; +} + +#tickets_legend .legend { + border: 1px solid #000000; + padding: 2px; + margin-bottom: 2px; +} + +/** Ticket display */ +#ticket { + background: #ffd; + border: 1px outset #996; + margin-top: 1em; + padding: .5em 1em; + position: relative; +} + +#ticket table.properties { + width: 100%; +} diff -urN projectpier-0.8.0.3/public/assets/themes/default/stylesheets/project/tickets.css projectpier-0.8.0.3-trackerpatch/public/assets/themes/default/stylesheets/project/tickets.css --- projectpier-0.8.0.3/public/assets/themes/default/stylesheets/project/tickets.css Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/public/assets/themes/default/stylesheets/project/tickets.css Wed Aug 6 22:00:31 2008 @@ -0,0 +1,40 @@ +/** Tickets display **/ + +#tickets .critical, #tickets_legend .critical { + background-color: #ffbaba; +} + +#tickets .major, #tickets_legend .major { + background-color: #fcffb1; +} + +#tickets .minor, #tickets_legend .minor { + background-color: #d6e7ff; +} + +#tickets .trivial, #tickets_legend .trivial { + background-color: #ffffff; +} + +#tickets_legend { + width: 120px; +} + +#tickets_legend .legend { + border: 1px solid #000000; + padding: 2px; + margin-bottom: 2px; +} + +/** Ticket display */ +#ticket { + background: #ffd; + border: 1px outset #996; + margin-top: 1em; + padding: .5em 1em; + position: relative; +} + +#ticket table.properties { + width: 100%; +} diff -urN projectpier-0.8.0.3/public/assets/themes/ebrentnelson/stylesheets/project/tickets.css projectpier-0.8.0.3-trackerpatch/public/assets/themes/ebrentnelson/stylesheets/project/tickets.css --- projectpier-0.8.0.3/public/assets/themes/ebrentnelson/stylesheets/project/tickets.css Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/public/assets/themes/ebrentnelson/stylesheets/project/tickets.css Wed Aug 6 22:00:31 2008 @@ -0,0 +1,40 @@ +/** Tickets display **/ + +#tickets .critical, #tickets_legend .critical { + background-color: #ffbaba; +} + +#tickets .major, #tickets_legend .major { + background-color: #fcffb1; +} + +#tickets .minor, #tickets_legend .minor { + background-color: #d6e7ff; +} + +#tickets .trivial, #tickets_legend .trivial { + background-color: #ffffff; +} + +#tickets_legend { + width: 120px; +} + +#tickets_legend .legend { + border: 1px solid #000000; + padding: 2px; + margin-bottom: 2px; +} + +/** Ticket display */ +#ticket { + background: #ffd; + border: 1px outset #996; + margin-top: 1em; + padding: .5em 1em; + position: relative; +} + +#ticket table.properties { + width: 100%; +} diff -urN projectpier-0.8.0.3/public/assets/themes/goCollab/stylesheets/project/tickets.css projectpier-0.8.0.3-trackerpatch/public/assets/themes/goCollab/stylesheets/project/tickets.css --- projectpier-0.8.0.3/public/assets/themes/goCollab/stylesheets/project/tickets.css Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/public/assets/themes/goCollab/stylesheets/project/tickets.css Wed Aug 6 22:00:31 2008 @@ -0,0 +1,40 @@ +/** Tickets display **/ + +#tickets .critical, #tickets_legend .critical { + background-color: #ffbaba; +} + +#tickets .major, #tickets_legend .major { + background-color: #fcffb1; +} + +#tickets .minor, #tickets_legend .minor { + background-color: #d6e7ff; +} + +#tickets .trivial, #tickets_legend .trivial { + background-color: #ffffff; +} + +#tickets_legend { + width: 120px; +} + +#tickets_legend .legend { + border: 1px solid #000000; + padding: 2px; + margin-bottom: 2px; +} + +/** Ticket display */ +#ticket { + background: #ffd; + border: 1px outset #996; + margin-top: 1em; + padding: .5em 1em; + position: relative; +} + +#ticket table.properties { + width: 100%; +} diff -urN projectpier-0.8.0.3/public/assets/themes/goCollab_Monochrome/stylesheets/project/tickets.css projectpier-0.8.0.3-trackerpatch/public/assets/themes/goCollab_Monochrome/stylesheets/project/tickets.css --- projectpier-0.8.0.3/public/assets/themes/goCollab_Monochrome/stylesheets/project/tickets.css Thu Jan 1 03:00:00 1970 +++ projectpier-0.8.0.3-trackerpatch/public/assets/themes/goCollab_Monochrome/stylesheets/project/tickets.css Wed Aug 6 22:00:31 2008 @@ -0,0 +1,40 @@ +/** Tickets display **/ + +#tickets .critical, #tickets_legend .critical { + background-color: #ffbaba; +} + +#tickets .major, #tickets_legend .major { + background-color: #fcffb1; +} + +#tickets .minor, #tickets_legend .minor { + background-color: #d6e7ff; +} + +#tickets .trivial, #tickets_legend .trivial { + background-color: #ffffff; +} + +#tickets_legend { + width: 120px; +} + +#tickets_legend .legend { + border: 1px solid #000000; + padding: 2px; + margin-bottom: 2px; +} + +/** Ticket display */ +#ticket { + background: #ffd; + border: 1px outset #996; + margin-top: 1em; + padding: .5em 1em; + position: relative; +} + +#ticket table.properties { + width: 100%; +} diff -urN projectpier-0.8.0.3/public/install/installation/templates/sql/mysql_schema.php projectpier-0.8.0.3-trackerpatch/public/install/installation/templates/sql/mysql_schema.php --- projectpier-0.8.0.3/public/install/installation/templates/sql/mysql_schema.php Mon Apr 28 15:06:10 2008 +++ projectpier-0.8.0.3-trackerpatch/public/install/installation/templates/sql/mysql_schema.php Wed Aug 6 22:00:31 2008 @@ -142,6 +142,14 @@ PRIMARY KEY (`message_id`,`user_id`) ) ENGINE=InnoDB ; +CREATE TABLE `project_categories` ( + `id` int(10) unsigned NOT NULL auto_increment, + `project_id` int(10) unsigned NOT NULL default '0', + `name` varchar(50) NOT NULL default '', + `description` varchar(255) default NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB ; + CREATE TABLE `project_companies` ( `project_id` int(10) unsigned NOT NULL default '0', `company_id` smallint(5) unsigned NOT NULL default '0', @@ -299,6 +307,30 @@ KEY `order` (`order`) ) ENGINE=InnoDB ; +CREATE TABLE `project_tickets` ( + `id` int(10) unsigned NOT NULL auto_increment, + `project_id` int(10) unsigned NOT NULL default '0', + `category_id` int(10) unsigned default NULL, + `assigned_to_company_id` smallint(5) unsigned default NULL, + `assigned_to_user_id` int(10) unsigned default NULL, + `summary` varchar(200) NOT NULL default '', + `type` enum('defect','enhancement','feature request') NOT NULL default 'defect', + `description` text , + `priority` enum('critical','major','minor','trivial') NOT NULL default 'major', + `is_private` tinyint(1) NOT NULL default '0', + `closed_on` datetime NOT NULL default '0000-00-00 00:00:00', + `closed_by_id` int(10) default NULL, + `created_on` datetime NOT NULL default '0000-00-00 00:00:00', + `created_by_id` int(10) unsigned default NULL, + `updated_on` datetime NOT NULL default '0000-00-00 00:00:00', + `updated_by_id` int(10) default NULL, + `updated` enum('settings','comment','attachment','open','closed') default NULL, + PRIMARY KEY (`id`), + KEY `created_on` (`created_on`), + KEY `closed_on` (`closed_on`), + KEY `project_id` (`project_id`) +) ENGINE=InnoDB ; + CREATE TABLE `project_users` ( `project_id` int(10) unsigned NOT NULL default '0', `user_id` int(10) unsigned NOT NULL default '0', @@ -309,6 +341,7 @@ `can_manage_milestones` tinyint(1) unsigned default '0', `can_upload_files` tinyint(1) unsigned default '0', `can_manage_files` tinyint(1) unsigned default '0', + `can_manage_tickets` tinyint(1) unsigned default '0', `can_assign_to_owners` tinyint(1) unsigned NOT NULL default '0', `can_assign_to_other` tinyint(1) unsigned NOT NULL default '0', PRIMARY KEY (`project_id`,`user_id`) @@ -354,6 +387,25 @@ KEY `project_id` (`project_id`), KEY `tag` (`tag`), KEY `object_id` (`rel_object_id`,`rel_object_manager`) +) ENGINE=InnoDB ; + +CREATE TABLE `ticket_changes` ( + `id` int(11) unsigned NOT NULL auto_increment, + `ticket_id` int(11) unsigned NOT NULL default '0', + `type` enum('status','priority','assigned to','summary','category','type','private','comment','attachment') NOT NULL, + `from_data` varchar(255) NOT NULL default '', + `to_data` varchar(255) NOT NULL default '', + `created_on` datetime NOT NULL default '0000-00-00 00:00:00', + `created_by_id` int(10) default NULL, + PRIMARY KEY (`id`), + KEY `created_on` (`created_on`), + KEY `ticket_id` (`ticket_id`) +) ENGINE=InnoDB ; + +CREATE TABLE `ticket_subscriptions` ( + `ticket_id` int(10) unsigned NOT NULL default '0', + `user_id` int(10) unsigned NOT NULL default '0', + PRIMARY KEY (`ticket_id`,`user_id`) ) ENGINE=InnoDB ; CREATE TABLE `user_im_values` (