<?php
/**
 * phpaga
 *
 * Project management functionality.
 *
 * This file contains the necessary routines to manage projects.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @version $Id: projects.php 878 2007-10-31 22:11:00Z florian $
 *
 * Copyright (c) 2002, Florian Lanthaler <florian@phpaga.net>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *    * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in
 *      the documentation and/or other materials provided with the
 *      distribution.
 *
 *    * Neither the name of Florian Lanthaler nor the names of his
 *      contributors may be used to endorse or promote products derived
 *      from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


/**
 * Gets all projects matching the search parmeters
 * (from $offset to $limit, if set)
 *
 * @param array  $rows      Array containing the results
 * @param int    $count     Number of records matching the query
 * @param string $duration  Total duration of matching projects
 * @param array  $prjInfo   Project information
 * @param array  $prjIds    IDs of the resulting projects
 * @param int    $order     Specifies the order by field
 *
 * @param int    $offset    Offset (optional)
 * @param int    $limit     Limit (optional)
 *
 * @return array $rows      Array containing the results. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @author Mark Parssey <markpa@users.sourceforge.net>
 * @since phpaga 0.3
 */

function phpaga_projects_search(&$count,
                                &$duration,
                                &$prjIds,
                                $prjInfo,
                                $order = 0,
                                $offset = null,
                                $limit = null)
{

    $count = 0;
    $rows = array();
    $prjIds = array();
    $where = array();
    $where_clause = null;
    $duration = '0';
    $qparam = array();

    /* check if current user can view all projects or only those she is a member of */

    $viewall = (phpaga_user_hasperm(PHPAGA_PERM_VIEW_OTHERPROJECTS)
                || phpaga_user_hasperm(PHPAGA_PERM_MANAGE_OTHERPROJECTS));

    /* if the current user can only view projects she is a member of, brutally
     * overwrite the pe_id prjInfo option with her pe_id
     */

    if (!$viewall)
        $prjInfo['pe_id'] = $_SESSION['auth_user']['pe_id'];

    $countquery = 'SELECT COUNT(projects.prj_id) AS count_prj FROM projects ';

    $query = 'SELECT projects.prj_id, prj_title, prj_startdate, prj_estimatedend,
        prj_deadline, prj_enddate, projects.cpn_id, cpn_name, prjcat_title,
        prjstat_id, prjstat_title, projects.prj_status, prj_priority,
        sum(op_duration) as prj_duration, prj_desc, prj_billabletype
        FROM projects
        LEFT JOIN companies ON companies.cpn_id = projects.cpn_id
        LEFT JOIN projectstatus ON projectstatus.prjstat_id = projects.prj_status
        LEFT JOIN projectcat ON projectcat.prjcat_id = projects.prjcat_id
        LEFT JOIN persons on persons.pe_id = projects.pe_id
        LEFT JOIN operations on operations.prj_id = projects.prj_id ';

    /* if person being searched for check project (created by, project
     * manager) or project_members */

    if (isset($prjInfo['pe_id']) && is_numeric($prjInfo['pe_id'])) {

        $countquery .= 'LEFT JOIN project_members pm on (pm.prj_id = projects.prj_id and pm.pe_id = ?) ';
        $query .= 'LEFT JOIN project_members pm on (pm.prj_id = projects.prj_id and pm.pe_id = ?) ';
        $qparam[] = $prjInfo['pe_id'];

        $where[] = '(projects.pe_id = ? OR pm.pe_id = ? OR projects.prj_manager_pe_id = ?)';
        $qparam[] = $prjInfo['pe_id'];
        $qparam[] = $prjInfo['pe_id'];
        $qparam[] = $prjInfo['pe_id'];
    }

    if (isset($prjInfo['bill_id']) && is_numeric($prjInfo['bill_id'])) {
        $where[] = 'projects.bill_id = ?';
        $qparam[] = $prjInfo['bill_id'];
    }

    if (isset($prjInfo['prj_id']) && is_numeric($prjInfo['prj_id'])) {
        $where[] = 'projects.prj_id = ?';
        $qparam[] = $prjInfo['prj_id'];
    }

    if (isset($prjInfo['prjcat_id']) && is_numeric($prjInfo['prjcat_id'])) {
        $where[] = 'projects.prjcat_id = ?';
        $qparam[] = $prjInfo['prjcat_id'];
    }

    if (isset($prjInfo['cpn_id']) && is_numeric($prjInfo['cpn_id'])) {
        $where[] = 'projects.cpn_id = ?';
        $qparam[] = $prjInfo['cpn_id'];
    }

    if (isset($prjInfo['cpn_id_issuebill']) && is_numeric($prjInfo['cpn_id_issuebill'])) {
        $where[] = 'projects.cpn_id_issuebill = ?';
        $qparam[] = $prjInfo['cpn_id_issuebill'];
    }

    if (isset($prjInfo['prj_status']) && is_numeric($prjInfo['prj_status'])) {
        $where[] = 'projects.prj_status = ?';
        $qparam[] = $prjInfo['prj_status'];
    }

    if (isset($prjInfo['prj_priority']) && is_numeric($prjInfo['prj_priority'])) {
        $where[] = 'projects.prj_priority = ?';
        $qparam[] = $prjInfo['prj_priority'];
    }

    if (isset($prjInfo['prj_billabletype']) && is_numeric($prjInfo['prj_billabletype'])) {
        $where[] = 'projects.prj_billabletype = ?';
        $qparam[] = $prjInfo['prj_billabletype'];
    }

    if (isset($prjInfo['prj_desc']) && strlen($prjInfo['prj_desc'])) {
        $where[] = 'projects.prj_desc LIKE ?';
        $qparam[] = '%'.$prjInfo['prj_desc'].'%';
    }

    if (isset($prjInfo['prj_title']) && strlen($prjInfo['prj_title'])) {
        $where[] = 'projects.prj_title LIKE ?';
        $qparam[] = '%'.$prjInfo['prj_title'].'%';
    }

    if (isset($prjInfo['prj_startdatefrom']) && strlen($prjInfo['prj_startdatefrom'])) {
        $where[] = 'projects.prj_startdate >= ?';
        $qparam[] = $prjInfo['prj_startdatefrom'];
    }

    if (isset($prjInfo['prj_startdateto']) && strlen($prjInfo['prj_startdateto'])) {
        $where[] = 'projects.prj_startdate <= ?';
        $qparam[] = $prjInfo['prj_startdateto'].' 23:59:59';
    }

    if (count($where)) {
        $h = sprintf('WHERE %s ', implode(' AND ', $where));
        $countquery .= $h;
        $query .= $h;
    }

    $query .= 'GROUP BY projects.prj_id, prj_title, prj_startdate, prj_estimatedend,
        prj_deadline, prj_enddate, projects.cpn_id, cpn_name, prjcat_title,
        prjstat_id, prjstat_title, projects.prj_status, prj_priority, prj_desc, prj_billabletype
        ORDER BY ';

    if (!strlen($order) || ($order == 0) || !is_numeric($order))
        $order = ORDER_PRJ_STARTDATE_DESC;

    switch ($order) {

        case ORDER_PRJ_PR_ASC:
            $query .= 'prj_title ASC';
            break;
        case ORDER_PRJ_PR_DESC:
            $query .= 'prj_title DESC';
            break;
        case ORDER_PRJ_PRJSTATUS_ASC:
            $query .= 'prjstat_title ASC';
            break;
        case ORDER_PRJ_PRJSTATUS_DESC:
            $query .= 'prjstat_title DESC';
            break;
        case ORDER_PRJ_PRIORITY_ASC:
            $query .= 'prj_priority ASC';
            break;
        case ORDER_PRJ_PRIORITY_DESC:
            $query .= 'prj_priority DESC';
            break;
        case ORDER_PRJ_CUSTOMER_ASC:
            $query .= 'cpn_name ASC';
            break;
        case ORDER_PRJ_CUSTOMER_DESC:
            $query .= 'cpn_name DESC';
            break;
        case ORDER_PRJ_STARTDATE_ASC:
            $query .= 'prj_startdate ASC';
            break;
        case ORDER_PRJ_STARTDATE_DESC:
            $query .= 'prj_startdate DESC';
            break;
        case ORDER_PRJ_PROJENDDATE_ASC:
            $query .= 'prj_estimatedend ASC';
            break;
        case ORDER_PRJ_PROJENDDATE_DESC:
            $query .= 'prj_estimatedend DESC';
            break;
        case ORDER_PRJ_PROJDEADLINE_ASC:
            $query .= 'prj_deadline ASC';
            break;
        case ORDER_PRJ_PROJDEADLINE_DESC:
            $query .= 'prj_deadline DESC';
            break;
        case ORDER_PRJ_PRJCAT_ASC:
            $query .= 'prjcat_title ASC';
            break;
        case ORDER_PRJ_PRJCAT_DESC:
            $query .= 'prjcat_title DESC';
            break;
        case ORDER_PRJ_DURATION_ASC:
            $query .= 'prj_duration ASC';
            break;
        case ORDER_PRJ_DURATION_DESC:
            $query .= 'prj_duration DESC';
            break;
        default:
            $query .= 'prj_startdate DESC';
            break;
    }

    $d = new PhPagaDbData;

    $prj_count = $d->queryOneVal($countquery, $qparam);

    if (!PhPagaError::isError($prj_count))
        $count = $prj_count;

    $projects = $d->queryAll($query, $qparam, $offset, $limit);

    if (PhPagaError::isError($projects) || !count($projects))
        return $projects;

    foreach ($projects as $qrow) {

        phpaga_sanitizearray($qrow);

        /* is the project overdue? */

        if (isset($qrow['prj_deadline']) && strlen($qrow['prj_deadline'])
            && (mktime () >= strtotime($qrow['prj_deadline']))
            && (!isset($qrow['prj_enddate']) || ($qrow['prj_enddate'] == '')))

            $qrow['prj_overdue'] = true;
        else
            $qrow['prj_overdue'] = false;

        $duration += $qrow['prj_duration'];
        $rows[] = $qrow;
        $prjIds[] = $qrow['prj_id'];
    }

    phpaga_sanitizearray($rows);
    return $rows;
}


/**
 * Gets a project's information
 *
 * @param int    $prjID   Project ID
 *
 * @return array $peInfo Array containing the results. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.1
 */

function phpaga_projects_getInfo($prjID)
{

    global $phpaga_projects_billabletype;

    $prjInfo = array();

    if (!isset($prjID) || (!is_numeric($prjID)))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_PROJECT_INVALIDID);

    $query = 'SELECT p.prj_id, p.prj_title, p.prj_desc, p.prj_startdate,
        p.prj_estimatedend, p.prj_deadline, p.prj_enddate, p.prj_status, p.prj_priority,
        p.cpn_id, cpn.cpn_name, prjcat_title,
        p.prjcat_id, prjstat_title, p.pe_id,
        p.cpn_id_issuebill, cpnbill.cpn_name as cpn_name_issuebill,
        persons.pe_lastname, persons.pe_firstname,
        p.prj_planned_manhours, p.prj_planned_cost_manhours,
        p.prj_planned_cost_material, p.prj_prj_id, p.prj_billabletype,
        ppr.prj_title as parent_prj_title,
        p.quot_id, quot_number, quot_date,
        p.prj_manager_pe_id, persons2.pe_lastname as prj_manager_lastname,
        persons2.pe_firstname as prj_manager_firstname
        FROM projects p
        LEFT JOIN projectcat ON projectcat.prjcat_id = p.prjcat_id
        LEFT JOIN companies cpn on cpn.cpn_id = p.cpn_id
        LEFT JOIN companies cpnbill on cpnbill.cpn_id = p.cpn_id_issuebill
        LEFT JOIN projectstatus on projectstatus.prjstat_id = p.prj_status
        LEFT JOIN persons ON persons.pe_id = p.pe_id
        LEFT JOIN persons persons2 ON persons2.pe_id = p.prj_manager_pe_id
        LEFT JOIN projects ppr ON ppr.prj_id = p.prj_prj_id
        LEFT JOIN quotations q ON q.quot_id = p.quot_id
        WHERE p.prj_id = ?';

    $d = new PhPagaDbData;

    $prjInfo = $d->queryRow($query, $prjID);

    if (PhPagaError::isError($prjInfo) || !count($prjInfo))
        return $prjInfo;

    phpaga_sanitizearray($prjInfo);

    if ($prjInfo['prj_startdate'] == '0000-00-00')
        $prjInfo['prj_startdate'] = '';

    if ($prjInfo['prj_estimatedend'] == '0000-00-00')
        $prjInfo['prj_estimatedend'] = '';

    if ($prjInfo['prj_deadline'] == '0000-00-00')
        $prjInfo['prj_deadline'] = '';

    if ($prjInfo['prj_enddate'] == '0000-00-00')
        $prjInfo['prj_enddate'] = '';

    $prjInfo['prj_owner'] = phpaga_persons_formatname($prjInfo['pe_lastname'], $prjInfo['pe_firstname']);
    $prjInfo['prj_manager'] = phpaga_persons_formatname($prjInfo['prj_manager_lastname'], $prjInfo['prj_manager_firstname']);

    /* If the prj_billabletype flag is not set, make the project
     * billable by default - this is to insure that data from previous
     * versions is treated as billable
     */

    if (!isset($prjInfo['prj_billabletype']) || ($prjInfo['prj_billabletype'] == null) || !strlen($prjInfo['prj_billabletype']))
        $prjInfo['prj_billabletype'] = PHPAGA_BILLABLETYPE_YES;

    if (isset($phpaga_projects_billabletype[$prjInfo['prj_billabletype']]))
        $prjInfo['prj_billabletype_text'] = $phpaga_projects_billabletype[$prjInfo['prj_billabletype']];
    else
        $prjInfo['prj_billabletype_text'] = _('unknown');

    if (isset($prjInfo['prj_deadline']) && strlen($prjInfo['prj_deadline'])
        && (mktime () >= strtotime($prjInfo['prj_deadline']))
        && (!isset($prjInfo['prj_enddate']) || ($prjInfo['prj_enddate'] == '')))

        $prjInfo['prj_overdue'] = true;
    else
        $prjInfo['prj_overdue'] = false;

    $opInfo = array('prj_id' => $prjID);
    $oprows = array();
    $opcount = 0;
    $opduration = 0;
    $opIds = array();
    $humancost = array();
    $prjInfo['humancost'] = array();

    $oprows = phpaga_operations_search($opcount, $opduration, $opIds, $opInfo);

    if (PhPagaError::isError($oprows)) {
        $prjInfo['operations'] = array();
        $sum_time = 0;
    } else {
        $prjInfo['operations'] = $oprows;
        $sum_time = phpaga_duration2minutes($opduration);

        $humancost = phpaga_operations_gethumancost($opIds);
        if (!PhPagaError::isError($humancost))
            $prjInfo['humancost'] = $humancost;
        else
            $humancost = array();
    }

    $prjInfo['prj_duration_minutes'] = $sum_time;

    if (isset($prjInfo['prj_planned_manhours']) && is_numeric($prjInfo['prj_planned_manhours'])
        && ($prjInfo['prj_planned_manhours'] > 0))

        $prjInfo['prj_perc_manhours'] = ($sum_time / ($prjInfo['prj_planned_manhours'] * 60)) * 100;
    else
        $prjInfo['prj_perc_manhours'] = null;

    if (isset($prjInfo['prj_planned_cost_manhours']) && is_numeric($prjInfo['prj_planned_cost_manhours'])
        && ($prjInfo['prj_planned_cost_manhours'] > 0))
    {
        if (isset($humancost['total_amount']) && is_numeric($humancost['total_amount']))
            $prjInfo['prj_perc_cost_manhours'] = ($humancost['total_amount'] / $prjInfo['prj_planned_cost_manhours']) * 100;
        else
            $prjInfo['prj_perc_cost_manhours'] = null;
    }
    else
        $prjInfo['prj_perc_cost_manhours'] = null;

    $matInfo = array('prj_id' => $prjID);
    $materials = array();

    $materials = phpaga_materials_getmaterials($matInfo);
    if (PhPagaError::isError($materials))
        $prjInfo['materials'] = array('total_amount' => 0,
                                      'details' => array());
    else {

        $prjInfo['materials'] = array('total_amount' => 0,
                                      'details' => $materials);

        foreach($materials as $m)
            if (isset($m['mat_price']) && is_numeric($m['mat_price']))
                $prjInfo['materials']['total_amount'] += $m['mat_price'];
    }

    if (isset($prjInfo['prj_planned_cost_material']) && is_numeric($prjInfo['prj_planned_cost_material'])
        && ($prjInfo['prj_planned_cost_material'] > 0))
    {
        if (isset($prjInfo['materials']['total_amount']) && is_numeric($prjInfo['materials']['total_amount']))
            $prjInfo['prj_perc_cost_material'] = ($prjInfo['materials']['total_amount'] / $prjInfo['prj_planned_cost_material']) * 100;
        else
            $prjInfo['prj_perc_cost_material'] = null;
    }
    else
        $prjInfo['prj_perc_cost_material'] = null;

    $expInfo = array('prj_id' => $prjID);
    $expenses = array();

    $expenses = phpaga_expenses_getexpenses($expInfo);

    if (PhPagaError::isError($expenses))
        $prjInfo['expenses'] = array();
    else
        $prjInfo['expenses'] = $expenses;

    $prjInfo['prj_members'] = array();

    $query = 'SELECT project_members.pe_id,
        persons.pe_lastname, persons.pe_firstname,
        project_members.jcat_id, jcat_title, prjmem_added,
        prjmem_pe_id, pmpers.pe_lastname AS pe_lastname_owner,
        pmpers.pe_firstname AS pe_firstname_owner,
        prjmem_hourlyrate
        FROM project_members
        LEFT JOIN persons ON persons.pe_id = project_members.pe_id
        LEFT JOIN jobcat ON jobcat.jcat_id = project_members.jcat_id
        LEFT JOIN persons pmpers ON pmpers.pe_id = prjmem_pe_id
        WHERE prj_id = ?
        ORDER BY pe_lastname, pe_firstname';

    $m = $d->queryAll($query, $prjID);

    if (!PhPagaError::isError($m)) {

        foreach ($m as $mrow) {
            phpaga_sanitizearray($mrow);
            $mrow['pe_person'] = phpaga_persons_formatname($mrow['pe_lastname'], $mrow['pe_firstname']);
            $mrow['pe_person_owner'] = phpaga_persons_formatname($mrow['pe_lastname_owner'], $mrow['pe_firstname_owner']);
            $prjInfo['prj_members'][] = $mrow;
        }
    }

    $prjchilds = phpaga_projects_getChildProjects($prjID);

    if (!PhPagaError::isError($prjchilds))
        $prjInfo['childs'] = $prjchilds;

    $files = phpaga_files_getAllRel(PHPAGA_RELTYPE_PROJECT, $prjID);

    if (!PhPagaError::isError($files))
        $prjInfo['files'] = $files;

    $prjInfo['opcatlist'] = array();

    $opcats = array();
    $opcats = phpaga_projects_gethourlyrates($prjID);
    if (!PhPagaError::isError($opcats))
        $prjInfo['opcatlist'] = $opcats;

    return $prjInfo;
}


/**
 * Gets a list of a project's childs.
 *
 * @param int    $prj_id      Project ID
 *
 * @return array $prjchilds   Array containing the results. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.3
 */

function phpaga_projects_getChildProjects($prj_id)
{

    $prjchilds = array();

    if (!isset($prj_id) || (!is_numeric($prj_id)))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_PROJECT_INVALIDID);

    $d = new PhPagaDbData;

    $prjchilds = $d->queryAll('SELECT prj_id, prj_title FROM projects WHERE prj_prj_id = ?', $prj_id);

    return $prjchilds;
}


/**
 * Gets all relations (parents and childs) of a specific project.
 *
 * @param int    $prj_id      Project ID
 * @param int    $prj_prj_id  Parent project ID
 * @param array  &$relations  Array containing the results.
 *
 * @return bool  $result      True. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.3
 */

function phpaga_projects_getParentChildProjects($prj_id, $prj_prj_id, &$relations)
{

    $qparam = array();

    if (!is_array($relations))
        $relations = array();

    if (!isset($prj_id) || (!is_numeric($prj_id)))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_PROJECT_INVALIDID);

    $d = new PhPagaDbData;

    $query = 'SELECT prj_id, prj_title, prj_prj_id FROM projects WHERE prj_prj_id = ?';
    $qparam[] = $prj_id;

    if (is_numeric($prj_prj_id) && !isset($relations[$prj_prj_id])) {
        $query .= ' OR prj_id = ?';
        $qparam[] = $prj_prj_id;
    }

    $r = $d->queryAll($query, $qparam);

    if (PhPagaError::isError($r))
        return $r;

    foreach ($r as $qrow) {
        $relations[$qrow["prj_id"]] = $qrow;

        if ($qrow["prj_prj_id"] != $prj_prj_id)
            $p = $qrow["prj_prj_id"];
        else
            $p = null;

        $fres = phpaga_projects_getParentChildProjects ($qrow['prj_id'], $p, $relations);

        if (PhPagaError::isError($fres))
            return $fres;
    }

    return true;
}


/**
 * Draws a graph of all relations (parents and childs) of a specific
 * project.
 *
 * @param  int     $prj_id      Project ID
 * @param  bool    $map         Output image map?
 *
 * @return string  $coord       Image map coordinates (if $map is true). A PhPagaError object on failure
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.3
 */

function phpaga_projects_getRelationGraph($prj_id, $map = false)
{

    if (!defined("PHPAGA_GRAPHVIZ_ENABLED") || !PHPAGA_GRAPHVIZ_ENABLED)
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_DISABLED);

    $relations = array();
    $p = array();
    $graphsource = null;
    $graphnodes = array();
    $graphedges = array();
    $ctype = null;
    $data = null;

    if ($map)
        $graphformat = "cmapx";
    else {
        $graphformat = PHPAGA_IMAGETYPE;

        if ($graphformat == 'jpg')
            $ctype = 'image/jpeg';
        else
            $ctype = 'image/'.$graphformat;
    }

    $d = new PhPagaDbData;;

    $prjInfo = $d->queryRow('SELECT prj_id, prj_title, prj_prj_id FROM projects WHERE prj_id = ?', $prj_id);

    if (PhPagaError::isError($prjInfo) || !is_array($prjInfo) || !count($prjInfo))
        return $prjInfo;

    $relations[$prj_id] = array("prj_id" => $prj_id,
                                "prj_title" => $prjInfo["prj_title"],
                                "prj_prj_id" => $prjInfo["prj_prj_id"]);

    $result = phpaga_projects_getParentChildProjects($prj_id, $prjInfo["prj_prj_id"], $relations);

    if (PhPagaError::isError($result))
        return $result;

    foreach ($relations as $rel) {

        if (isset($rel["prj_id"]) && is_numeric($rel["prj_id"])) {

            if (!isset($p[$rel["prj_id"]])) {

                if ($rel["prj_id"] == $prj_id) {
                    $color = "#00ff00";
                    $fillcolor = "#ddffdd";
                } else {
                    $color = "#aaaaaa";
                    $fillcolor = "#eeeeee";
                }

                $graphnodes[] = array("node" => $rel["prj_id"],
                                      "label" => phpaga_jsmessage_sanitizestr($rel["prj_title"]),
                                      "color" => $color,
                                      "fillcolor" => $fillcolor);

                $p[$rel["prj_id"]] = true;
            }

            if (isset($rel["prj_prj_id"]) && strlen($rel["prj_prj_id"]))
                $graphedges[] = array("o" => $rel["prj_prj_id"],
                                      "d" => $rel["prj_id"]);
        }
    }

    if (count($graphnodes)) {

        $graphsource = "digraph G {\n";

        foreach ($graphnodes as $g)
            $graphsource .= sprintf("\"%s\" [ label=\"%s\",shape=\"box\",color=\"%s\",fontsize=\"10\",height=\"0.1\",width=\"0.1\",fillcolor=\"%s\",style=\"filled,solid\",URL=\"project.php?id=%s\" ];\n",
                                    $g['node'],
                                    $g['label'],
                                    $g['color'],
                                    $g['fillcolor'],
                                    $g['node']);

        foreach ($graphedges as $e)
            $graphsource .= sprintf("\"%s\" -> \"%s\" [ color=\"#666666\" ];\n",
                                    $e['o'],
                                    $e['d']);

        $graphsource .= "}\n";

        $datafile = tempnam(PHPAGA_TMPDIR, 'graphv');

        if ($fh = fopen($datafile, "wb")) {
            fwrite($fh, $graphsource);
            fclose($fh);
        }

        $outfile = $datafile.'.'.$graphformat;
        $cmd = PHPAGA_BIN_DOT.' -T'.escapeshellarg($graphformat).' -o' .escapeshellarg($outfile).' '.escapeshellarg($datafile);
        @`$cmd`;
        @unlink($datafile);

        if (file_exists($outfile) && ($fh = fopen($outfile, 'rb'))) {
            $data = fread($fh, filesize($outfile));
            fclose($fh);
            @unlink($outfile);
        }

        if ($map)
            return $data;

        header("Expires: Thu, 26 Jul 2007 11:00:00 GMT");
        header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
        header("Cache-Control: no-cache, must-revalidate");
        header("Pragma: no-cache");
        header("Cache-Control: post-check=0, pre-check=0", false);
        header("Content-Disposition: filename=relations.".$graphformat);
        header('Content-Type: '.$ctype);
        header('Content-Length: '.strlen($data));

        print($data);
    }
}


/**
 * Gets a project's total duration
 *
 * @param  int     $prjID    Project ID
 *
 * @return string  $duration Duration. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.1
 */

function phpaga_projects_getDuration($prjID)
{
    $duration = null;

    if (!isset($prjID) || (!is_numeric($prjID)))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_PROJECT_INVALIDID);

    $d = new PhPagaDbData;

    $duration = $d->queryOneVal('SELECT SUM(op_duration) AS duration FROM operations WHERE prj_id = ?', $prjID);

    if (PhPagaError::isError($duration))
        return $duration;

    if (!strlen($duration))
        $duration = phpaga_minutes2duration(0);
    else
        $duration = phpaga_minutes2duration($duration);

    return $duration;
}


/**
 * Gets a list of project categories from the database and returns
 * them as the content of an array.
 *
 * @return array $prjcatList  Array of project categories. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.1
 */

function phpaga_projects_getCategoryList()
{

    $prjcatList = array();

    $d = new PhPagaDbData;

    $r = $d->queryAll('SELECT prjcat_id, prjcat_title FROM projectcat ORDER BY prjcat_title');

    if (PhPagaError::isError($r))
        return $r;

    foreach ($r as $qrow) {
        phpaga_sanitizearray($qrow);
        $prjcatList[$qrow['prjcat_id']] = $qrow['prjcat_title'];
    }

    return $prjcatList;
}


/**
 * Gets a list of project stati from the database and returns them as
 * the content of an array.
 *
 * @return array $prjstatList  Array of project stati. A PhPagaError on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.1
 */

function phpaga_projects_getStatusList()
{
    $prjstatList = array();

    $d = new PhPagaDbData;

    $r = $d->queryAll('SELECT prjstat_id, prjstat_title FROM projectstatus ORDER BY prjstat_title');

    if (PhPagaError::isError($r))
        return $r;

    foreach ($r as $qrow) {
        phpaga_sanitizearray($qrow);
        $prjstatList[$qrow['prjstat_id']] = $qrow['prjstat_title'];
    }

    return $prjstatList;
}


/**
 * Checks if all fields of a project contain valid values.
 *
 * @param  array  $prjInfo  Array containing the project's information.
 *
 * @return bool   $errors  True. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.1
 */

function phpaga_projects_validdata ($prjInfo)
{

    $errors = array();

    if (!is_array($prjInfo))
        $prjInfo = array($prjInfo);

    if (!phpaga_checkdate($prjInfo["prj_startdate"]))
        $errors[] = PHPAGA_ERR_PRJ_STARTDATE;

    if (!phpaga_checkdate($prjInfo["prj_estimatedend"]))
        $errors[] = PHPAGA_ERR_PRJ_ESTIMATEDENDDATE;

    if (!phpaga_checkdate($prjInfo["prj_deadline"]))
        $errors[] = PHPAGA_ERR_PRJ_DEADLINEDATE;

    if (!isset($prjInfo["prj_title"]) || !strlen(trim($prjInfo["prj_title"])))
        $errors[] = PHPAGA_ERR_PROJECT_INVALIDTITLE;

    if (isset($prjInfo["cpn_id"]) && !is_numeric($prjInfo["cpn_id"]))
        $errors[] = PHPAGA_ERR_COMPANY_INVALIDID;

    if (isset($prjInfo["prjcat_id"]) && !is_numeric($prjInfo["prjcat_id"]))
        $errors[] = PHPAGA_ERR_PRJCAT_INVALIDID;

    if (!isset($prjInfo["pe_id"]) || !is_numeric($prjInfo["pe_id"]))
        $errors[] = PHPAGA_ERR_PERSON_INVALIDID;

    if (isset($prjInfo["prj_prj_id"]) && strlen($prjInfo["prj_prj_id"]) && !is_numeric($prjInfo["prj_prj_id"]))
        $errors[] = PHPAGA_ERR_INVALID_PARENTPRJ;

    if (isset($prjInfo["prj_planned_manhours"])
        && strlen($prjInfo["prj_planned_manhours"]) && !is_numeric($prjInfo["prj_planned_manhours"]))

        $errors[] = PHPAGA_ERR_INVALID_MANHOURS;

    if (isset($prjInfo["prj_planned_cost_manhours"])
        && strlen($prjInfo["prj_planned_cost_manhours"]) && !is_numeric($prjInfo["prj_planned_cost_manhours"]))

        $errors[] = PHPAGA_ERR_INVALID_COST_MANHOURS;

    if (isset($prjInfo["prj_planned_cost_material"])
        && strlen($prjInfo["prj_planned_cost_material"]) && !is_numeric($prjInfo["prj_planned_cost_material"]))

        $errors[] = PHPAGA_ERR_INVALID_COST_MATERIAL;

    if (isset($prjInfo["prj_billabletype"])
        && strlen($prjInfo["prj_billabletype"]) && !is_numeric($prjInfo["prj_billabletype"]))

        $errors[] = PHPAGA_ERR_INVALID_BILLABLETYPE;

    if (count($errors))
        return PhPagaError::raiseErrorByCode($errors);

    return true;
}


/**
 * Adds a project to the database
 *
 * @param array  $prjInfo Array containing the project information
 *
 * @return int   $prjID  ID of the freshly inserted project. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.1
 */

function phpaga_projects_add($prjInfo)
{

    $prjID = null;

    $result = phpaga_projects_validdata ($prjInfo);

    if (PhPagaError::isError($result))
        return $result;

    $d = new PhPagaDbData;

    $prjID = $d->nextId('projects_prj_id');

    if (!isset($prjInfo['cpn_id_issuebill']) || (!is_numeric($prjInfo['cpn_id_issuebill'])))
        $prjInfo['cpn_id_issuebill'] = null;

    if (!isset($prjInfo['prj_title']) || !strlen($prjInfo['prj_title']))
        $prjInfo['prj_title'] = null;

    if (!isset($prjInfo['prj_desc']) || !strlen($prjInfo['prj_desc']))
        $prjInfo['prj_desc'] = null;

    if (!isset($prjInfo['prj_startdate']) || !strlen($prjInfo['prj_startdate']))
        $prjInfo['prj_startdate'] = null;

    if (!isset($prjInfo['prj_estimatedend']) || !strlen($prjInfo['prj_estimatedend']))
        $prjInfo['prj_estimatedend'] = null;

    if (!isset($prjInfo['prj_deadline']) || !strlen($prjInfo['prj_deadline']))
        $prjInfo['prj_deadline'] = null;

    if (!isset($prjInfo['prj_enddate']) || !strlen($prjInfo['prj_enddate']))
        $prjInfo['prj_enddate'] = null;

    if (!isset($prjInfo['prj_prj_id']) || !is_numeric($prjInfo['prj_prj_id']))
        $prjInfo['prj_prj_id'] = null;

    if (!isset($prjInfo['prj_manager_pe_id']) || !is_numeric($prjInfo['prj_manager_pe_id']))
        $prjInfo['prj_manager_pe_id'] = null;

    if (!isset($prjInfo['prj_planned_manhours']) || !is_numeric($prjInfo['prj_planned_manhours']))
        $prjInfo['prj_planned_manhours'] = null;

    if (!isset($prjInfo['prj_planned_cost_manhours']) || !is_numeric($prjInfo['prj_planned_cost_manhours']))
        $prjInfo['prj_planned_cost_manhours'] = null;

    if (!isset($prjInfo['prj_planned_cost_material']) || !is_numeric($prjInfo['prj_planned_cost_material']))
        $prjInfo['prj_planned_cost_material'] = null;

    if (!isset($prjInfo['prj_status']) || !strlen($prjInfo['prj_status']))
        $prjInfo['prj_status'] = PHPAGA_PROJECTSTATUS_OPEN;

    if (!isset($prjInfo['prj_billabletype']) || !is_numeric($prjInfo['prj_billabletype']))
        $prjInfo['prj_billabletype'] = null;

    if (!isset($prjInfo['prj_priority']) || !is_numeric($prjInfo['prj_priority']))
        $prjInfo['prj_priority'] = null;

    if (!isset($prjInfo['quot_id']) || !is_numeric($prjInfo['quot_id']))
        $prjInfo['quot_id'] = null;

    if (!isset($prjInfo['opcatlist']) || !is_array($prjInfo['opcatlist']))
        $prjInfo['opcatlist'] = array();

    $fields = array('prj_id' => $prjID,
                    'prj_title' => $prjInfo['prj_title'],
                    'prj_desc' => $prjInfo['prj_desc'],
                    'prj_startdate' => $prjInfo['prj_startdate'],
                    'prj_estimatedend' => $prjInfo['prj_estimatedend'],
                    'prj_deadline' => $prjInfo['prj_deadline'],
                    'prj_enddate' => $prjInfo['prj_enddate'],
                    'prj_status' => $prjInfo['prj_status'],
                    'cpn_id' => $prjInfo['cpn_id'],
                    'quot_id' => $prjInfo['quot_id'],
                    'prjcat_id' => $prjInfo['prjcat_id'],
                    'prj_planned_manhours' => $prjInfo['prj_planned_manhours'],
                    'prj_planned_cost_manhours' => $prjInfo['prj_planned_cost_manhours'],
                    'prj_planned_cost_material' => $prjInfo['prj_planned_cost_material'],
                    'pe_id' => $prjInfo['pe_id'],
                    'prj_prj_id' => $prjInfo['prj_prj_id'],
                    'cpn_id_issuebill' => $prjInfo['cpn_id_issuebill'],
                    'prj_billabletype' => $prjInfo['prj_billabletype'],
                    'prj_priority' => $prjInfo['prj_priority'],
                    'prj_manager_pe_id' => $prjInfo['prj_manager_pe_id']);

    $r = $d->insert('projects', $fields);

    if (PhPagaError::isError($r))
        return $r;

    $result = phpaga_projects_sethourlyrates($prjID, $prjInfo['opcatlist']);

    if (PhPagaError::isError($result))
        return $result;

    return $prjID;

}


/**
 * Updates a project
 *
 * @param array  $prjInfo Array containing the project information
 *
 * @return bool  $result  True. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.1
 */

function phpaga_projects_update($prjInfo)
{

    $result = phpaga_projects_validdata ($prjInfo);

    if (PhPagaError::isError($result))
        return $result;

    if (!isset($prjInfo['cpn_id_issuebill']) || (!is_numeric($prjInfo['cpn_id_issuebill'])))
        $prjInfo['cpn_id_issuebill'] = null;

    if (!isset($prjInfo['prj_title']) || !strlen($prjInfo['prj_title']))
        $prjInfo['prj_title'] = null;

    if (!isset($prjInfo['prj_desc']) || !strlen($prjInfo['prj_desc']))
        $prjInfo['prj_desc'] = null;

    if (!isset($prjInfo['prj_startdate']) || !strlen($prjInfo['prj_startdate']))
        $prjInfo['prj_startdate'] = null;

    if (!isset($prjInfo['prj_estimatedend']) || !strlen($prjInfo['prj_estimatedend']))
        $prjInfo['prj_estimatedend'] = null;

    if (!isset($prjInfo['prj_deadline']) || !strlen($prjInfo['prj_deadline']))
        $prjInfo['prj_deadline'] = null;

    if (!isset($prjInfo['prj_enddate']) || !strlen($prjInfo['prj_enddate']))
        $prjInfo['prj_enddate'] = null;

    if (!isset($prjInfo['prj_prj_id']) || !is_numeric($prjInfo['prj_prj_id']))
        $prjInfo['prj_prj_id'] = null;

    if (!isset($prjInfo['prj_manager_pe_id']) || !is_numeric($prjInfo['prj_manager_pe_id']))
        $prjInfo['prj_manager_pe_id'] = null;

    if (!isset($prjInfo['prj_planned_manhours']) || !is_numeric($prjInfo['prj_planned_manhours']))
        $prjInfo['prj_planned_manhours'] = null;

    if (!isset($prjInfo['prj_planned_cost_manhours']) || !is_numeric($prjInfo['prj_planned_cost_manhours']))
        $prjInfo['prj_planned_cost_manhours'] = null;

    if (!isset($prjInfo['prj_planned_cost_material']) || !is_numeric($prjInfo['prj_planned_cost_material']))
        $prjInfo['prj_planned_cost_material'] = null;

    if (!isset($prjInfo['prj_status']) || !strlen($prjInfo['prj_status']))
        $prjInfo['prj_status'] = PHPAGA_PROJECTSTATUS_OPEN;

    if (!isset($prjInfo['prj_billabletype']) || !is_numeric($prjInfo['prj_billabletype']))
        $prjInfo['prj_billabletype'] = null;

    if (!isset($prjInfo['prj_priority']) || !is_numeric($prjInfo['prj_priority']))
        $prjInfo['prj_priority'] = null;

    if (!isset($prjInfo['opcatlist']) || !is_array($prjInfo['opcatlist']))
        $prjInfo['opcatlist'] = array();

    $fields = array('prj_title' => $prjInfo['prj_title'],
                    'prj_desc' => $prjInfo['prj_desc'],
                    'prj_startdate' => $prjInfo['prj_startdate'],
                    'prj_estimatedend' => $prjInfo['prj_estimatedend'],
                    'prj_deadline' => $prjInfo['prj_deadline'],
                    'prj_enddate' => $prjInfo['prj_enddate'],
                    'prj_status' => $prjInfo['prj_status'],
                    'cpn_id' => $prjInfo['cpn_id'],
                    'prjcat_id' => $prjInfo['prjcat_id'],
                    'prj_planned_manhours' => $prjInfo['prj_planned_manhours'],
                    'prj_planned_cost_manhours' => $prjInfo['prj_planned_cost_manhours'],
                    'prj_planned_cost_material' => $prjInfo['prj_planned_cost_material'],
                    'pe_id' => $prjInfo['pe_id'],
                    'prj_prj_id' => $prjInfo['prj_prj_id'],
                    'cpn_id_issuebill' => $prjInfo['cpn_id_issuebill'],
                    'prj_billabletype' => $prjInfo['prj_billabletype'],
                    'prj_priority' => $prjInfo['prj_priority'],
                    'prj_manager_pe_id' => $prjInfo['prj_manager_pe_id']
        );

    $d = new PhPagaDbData;
    $r = $d->update('projects', $fields, 'prj_id = ?', array($prjInfo['prj_id']));

    if (PhPagaError::isError($r))
        return $r;

    $result = phpaga_projects_sethourlyrates($prjInfo['prj_id'], $prjInfo['opcatlist']);

    if (PhPagaError::isError($result))
        return $result;

    return true;
}


/**
 * Delete a project.
 *
 * @param  int     $prj_id       Project ID
 * @param  bool    $force        Force delete the project even if some
 *                               dependencies exist (in this case delete the
 *                               dependencies also). This does not work if
 *                               any of the operations associated to this
 *                               project are already billed.
 *
 * @return bool    $result      True. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.1.2
 */

function phpaga_projects_delete($prj_id, $force = false)
{
    if (!isset($prj_id) || !is_numeric($prj_id))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_PROJECT_INVALIDID);

    $d = new PhPagaDbData;

    /* If operations are filed for this project which are also linked
     * to a bill then this project cannot be deleted (not even when
     * $force is true).
     */

    $n = $d->queryOneVal('SELECT COUNT(prj_id) FROM operations WHERE prj_id = ? AND bill_id IS NOT NULL', $prj_id);

    if (PhPagaError::isError($n))
        return $n;

    if ($n > 0)
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_DEL_DEPENDENCIES);

    /* If $force is true the delete this project and all associated
     * operations, project memberships, and expenses
     */

    if ($force) {

        /* delete all
         * We need to remove the records from each table "manually"
         * as not all supported "database systems" can handle
         * relationships and triggers that could handle the
         * same job
         */

        $r = $d->beginTransaction();
        if (PhPagaError::isError($r))
            return $r;

        $del = array('DELETE FROM projects_hourlyrates WHERE prj_id = ?',
                     'DELETE FROM project_members WHERE prj_id = ?',
                     'DELETE FROM operations WHERE prj_id = ?',
                     'DELETE FROM expenses WHERE prj_id = ?',
                     'DELETE FROM material WHERE prj_id = ?');

        foreach ($del as $q) {

            $r = $d->execute($q, $prj_id);

            if (PhPagaError::isError($r)) {
                $d->rollback();
                return $r;
            }
        }

        /* Update all child projects */

        $r = $d->update('projects', array("prj_prj_id" => null), 'prj_prj_id = ?', $prj_id);

        if (PhPagaError::isError($r)) {
            $d->rollback();
            return $r;
        }

        $r = $d->execute('DELETE FROM projects WHERE prj_id = ?', $prj_id);

        if (PhPagaError::isError($r)) {
            $d->rollback();
            return $r;
        }

        $r = $d->commit();

        return $r;

    } else {

        /* $force is false, therefore remove the project only if there
         * are no other records depending on it
         */

        $depqueries = array('SELECT COUNT(prj_id) FROM operations WHERE prj_id = ?',
                            'SELECT COUNT(prj_id) FROM expenses WHERE prj_id = ?',
                            'SELECT COUNT(prj_id) FROM material WHERE prj_id = ?',
                            'SELECT COUNT(prj_id) FROM project_members WHERE prj_id = ?',
                            'SELECT COUNT(prj_id) FROM projects WHERE prj_prj_id = ?');

        foreach ($depqueries as $dpq) {

            $n = $d->queryOneVal($dpq, $prj_id);

            if (PhPagaError::isError($n))
                return $n;

            if ($n > 0)
                return PhPagaError::raiseErrorByCode(PHPAGA_ERR_DEL_DEPENDENCIES);
        }

        $r = $d->beginTransaction();
        if (PhPagaError::isError($r))
            return $r;

        $r = $d->execute('DELETE FROM projects_hourlyrates WHERE prj_id = ?', $prj_id);

        if (PhPagaError::isError($r)) {
            $d->rollback();
            return $r;
        }

        $r = $d->execute('DELETE FROM projects WHERE prj_id = ?', $prj_id);

        if (PhPagaError::isError($r)) {
            $d->rollback();
            return $r;
        }

        $r = $d->commit();

        return $r;
    }
}


/**
 * Gets a list of projects from the database and returns them as the
 * content of an array.
 *
 * @return array  $projectList  Array of projects. A PhPagaError onject on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.1
 */

function phpaga_projects_getProjectList()
{
    $projectList = array();
    $param = null;

    /* Check if current user can view all projects or only those she is a member of */

    $viewall = (phpaga_user_hasperm(PHPAGA_PERM_MANAGE_PROJECTS)
                || phpaga_user_hasperm(PHPAGA_PERM_VIEW_OTHERPROJECTS)
                || phpaga_user_hasperm(PHPAGA_PERM_MANAGE_OTHERPROJECTS));


    $d = new PhPagaDbData;

    $query = 'SELECT distinct p.prj_id, prj_title FROM projects p ';

    if (!$viewall) {
        $query .= 'LEFT JOIN project_members pm on pm.prj_id = p.prj_id WHERE (p.pe_id = ? OR pm.pe_id = ?)';
        $param = array($_SESSION['auth_user']['pe_id'], $_SESSION['auth_user']['pe_id']);
    }

    $query .= ' ORDER BY prj_title';

    $r = $d->queryAll($query, $param);

    if (PhPagaError::isError($r))
        return $r;

    foreach ($r as $qrow) {
        phpaga_sanitizearray($qrow);
        $projectList[$qrow['prj_id']] = $qrow['prj_title'];
    }

    return $projectList;
}


/**
 * Loads all project categories into an array.
 *
 * @return array  $prjcatInfo   Array containing the results. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.1
 */

function phpaga_projects_getPrjcats()
{
    $prjcatInfo = array();

    $d = new PhPagaDbData;

    $r = $d->queryAll('SELECT prjcat_id, prjcat_title FROM projectcat ORDER BY prjcat_title');

    if (PhPagaError::isError($r))
        return $r;

    foreach ($r as $qrow) {
        phpaga_sanitizearray($qrow);
        $prjcatInfo[] = $qrow;
    }

    return $prjcatInfo;
}


/**
 * Add a new project category.
 *
 * @param  string  $prjcat_title  Project category to be added
 *
 * @return int     $id            Category Id. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.1
 */

function phpaga_projects_addPrjcat ($prjcat_title)
{

    if (!isset($prjcat_title) || !strlen ($prjcat_title))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_PRJCAT_INVALIDTITLE);

    $d = new PhPagaDbData;
    $id = $d->nextId('projectcat_prjcat_id');

    $r = $d->insert('projectcat', array('prjcat_id' => $id,
                                        'prjcat_title' => $prjcat_title));

    if (PhPagaError::isError($r))
        return $r;

    return $id;
}


/**
 * Delete a project category.
 *
 * @param  int     $prjcat_id    Project category to be deleted
 *
 * @return bool    $result      True if successful. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.1
 */

function phpaga_projects_deletePrjcat($prjcat_id)
{
    if (!isset($prjcat_id) || !is_numeric($prjcat_id))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_PRJCAT_INVALIDID);

    $d = new PhPagaDbData;

    $n = $d->queryOneVal('SELECT COUNT(prj_id) FROM projects WHERE prjcat_id = ?', $prjcat_id);

    if (PhPagaError::isError($n))
        return $n;

    if ($n > 0)
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_DEL_DEPENDENCIES);

    $result = $d->execute('DELETE FROM projectcat WHERE prjcat_id = ?', $prjcat_id);

    if (PhPagaError::isError($result))
        return $result;

    return true;
}


/**
 * Loads all project stati into an array.
 *
 * @param bool    $assoc        True if an associative array should be returned.
 *                              Defaults to false.
 *
 * @return array  $prjstatInfo  Array containing the results. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.1
 */

function phpaga_projects_getPrjstatus($assoc = false)
{
    $prjstatInfo = array();

    $d = new PhPagaDbData;

    $r = $d->queryAll('SELECT prjstat_id, prjstat_title FROM projectstatus ORDER BY prjstat_title');

    if (PhPagaError::isError($r))
        return $r;

    foreach ($r as $qrow) {
        phpaga_sanitizearray($qrow);

        if (!$assoc)
            $prjstatInfo[] = $qrow;
        else
            $prjstatInfo[$qrow['prjstat_id']] = $qrow['prjstat_title'];
    }

    return $prjstatInfo;
}


/**
 * Adds a new project status.
 *
 * @param  string  $prjstat_title  Project status to be added
 *
 * @return int     $id             Status Id. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.1
 */

function phpaga_projects_addPrjstatus($prjstat_title)
{

    if (!isset($prjstat_title) || !strlen ($prjstat_title))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_PRJSTAT_INVALIDTITLE);

    $d = new PhPagaDbData;
    $id = $d->nextId('projectstatus_prjstat_id');

    $r = $d->insert('projectstatus', array('prjstat_id'=> $id, 'prjstat_title' => $prjstat_title));

    if (PhPagaError::isError($r))
        return $r;

    return $id;
}


/**
 * Deletes a project status.
 *
 * @param  int     $prjstat_id    Project status to be added
 *
 * @return bool    $result      True if successful. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.1
 */

function phpaga_projects_deletePrjstatus($prjstat_id)
{
    if (!isset($prjstat_id) || !is_numeric($prjstat_id))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_PRJSTAT_INVALIDID);

    $d = new PhPagaDbData;

    $n = $d->queryOneVal('SELECT COUNT(prj_id) FROM projects WHERE prj_status = ?', $prjstat_id);

    if (PhPagaError::isError($n))
        return $n;

    if ($n > 0)
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_DEL_DEPENDENCIES);

    $result = $d->execute('DELETE FROM projectstatus WHERE prjstat_id = ?', $prjstat_id);

    if (PhPagaError::isError($result))
        return $result;

    return true;
}


/**
 * Checks if all fields of a project member contain valid values.
 *
 * @param  array  $member  Array containing the project's information.
 *
 * @return bool   $errors  True. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.2
 */

function phpaga_projects_validmemberdata($member)
{

    $errors = array();

    if (!is_array($member))
        $member = array($member);

    if (isset($member["prj_id"]) && !is_numeric($member["prj_id"]))
        $errors[] = PHPAGA_ERR_PROJECT_INVALIDID;

    if (isset($member["jcat_id"]) && !is_numeric($member["jcat_id"]))
        $errors[] = PHPAGA_ERR_JCAT_INVALIDID;

    if (!isset($member["pe_id"]) || !is_numeric($member["pe_id"]))
        $errors[] = PHPAGA_ERR_PERSON_INVALIDID;

    if (isset($member["prjmem_hourlyrate"]) && strlen($member["prjmem_hourlyrate"]) &&
        !is_numeric($member["prjmem_hourlyrate"]))

        $errors[] = PHPAGA_ERR_INVALID_HOURLYRATE;

    if (count($errors))
        return PhPagaError::raiseErrorByCode($errors);

    return true;
}


/**
 * Adds a project member
 *
 * @param  array  $member  Array containing the membership information
 *
 * @return bool   $result  True. A PhPagaError on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.1
 */

function phpaga_projects_addmember($member)
{

    $result = phpaga_projects_validmemberdata($member);

    if (PhPagaError::isError($result))
        return $result;

    if (!isset($member['jcat_id']) || !is_numeric($member['jcat_id']))
        $member['jcat_id'] = null;

    if (!isset($member['prjmem_pe_id']) || !is_numeric($member['prjmem_pe_id']))
        $member['prjmem_pe_id'] = null;

    if (!isset($member['prjmem_hourlyrate'])
        || !is_numeric($member['prjmem_hourlyrate']))

        $member['prjmem_hourlyrate'] = null;

    $fields = array('prj_id' => $member['prj_id'],
                    'pe_id' => $member['pe_id'],
                    'jcat_id' => $member['jcat_id'],
                    'prjmem_pe_id' => $member['prjmem_pe_id'],
                    'prjmem_hourlyrate' => $member['prjmem_hourlyrate']);

    $d = new PhPagaDbData;

    $r = $d->insert('project_members', $fields);

    if (PhPagaError::isError($r))
        return $r;

    if (isset($member['opcats']) && is_array($member['opcats'])) {
        $result = phpaga_projects_sethourlyrates($member['prj_id'], $member['opcats'], $member['pe_id']);

        if (PhPagaError::isError($result))
            return $result;
    }

    return true;
}


/**
 * Updates a project member's information
 *
 * @param  array  $member  Array containing the membership information
 *
 * @return bool   $result  True. A PhPagaError on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.3
 */

function phpaga_projects_updatemember($member)
{

    $result = phpaga_projects_validmemberdata($member);

    if (PhPagaError::isError($result))
        return $result;

    if (!isset($member['jcat_id']) || !is_numeric($member['jcat_id']))
        $member['jcat_id'] = null;

    if (!isset($member['prjmem_pe_id']) || !is_numeric($member['prjmem_pe_id']))
        $member['prjmem_pe_id'] = null;

    if (!isset($member['prjmem_hourlyrate']) || !is_numeric($member['prjmem_hourlyrate']))
        $member['prjmem_hourlyrate'] = null;

    $fields = array('jcat_id' => $member['jcat_id'],
                    'prjmem_pe_id' => $member['prjmem_pe_id'],
                    'prjmem_hourlyrate' => $member['prjmem_hourlyrate']);

    $d = new PhPagaDbData;

    $r = $d->update('project_members', $fields, 'prj_id = ? AND pe_id = ?', array($member['prj_id'], $member['pe_id']));

    if (PhPagaError::isError($r))
        return $r;

    if (isset($member['opcats']) && is_array($member['opcats'])) {
        $result = phpaga_projects_sethourlyrates($member['prj_id'], $member['opcats'], $member['pe_id']);

        if (PhPagaError::isError($result))
            return $result;
    }

    return true;
}


/**
 * Fetches information about a specific member of a project.
 *
 * @param int    $prj_id       Project ID
 * @param int    $pe_id        Pe ID
 *
 * @return array $prj_members  Array containing the results. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.3
 */

function phpaga_project_getMemberInfo($prj_id, $pe_id)
{

    $member = array();

    if (!isset($prj_id) || !is_numeric($prj_id))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_PROJECT_INVALIDID);
    elseif (!isset($pe_id) || !is_numeric($pe_id))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_PERSON_INVALIDID);

    $query = 'SELECT project_members.pe_id,
        persons.pe_lastname, persons.pe_firstname,
        project_members.jcat_id, jcat_title, prjmem_added,
        prjmem_pe_id, pmpers.pe_lastname AS pe_lastname_owner,
        pmpers.pe_firstname AS pe_firstname_owner,
        prjmem_hourlyrate, project_members.prj_id
        FROM project_members
        LEFT JOIN persons ON persons.pe_id = project_members.pe_id
        LEFT JOIN jobcat ON jobcat.jcat_id = project_members.jcat_id
        LEFT JOIN persons pmpers ON pmpers.pe_id = prjmem_pe_id
        WHERE prj_id = ?
        AND project_members.pe_id = ?';

    $d = new PhPagaDbData;

    $member = $d->queryRow($query, array($prj_id, $pe_id));

    if (PhPagaError::isError($member) || !count($member))
        return $member;

    phpaga_sanitizearray($member);

    $member["pe_person"] = phpaga_persons_formatname($member["pe_lastname"], $member["pe_firstname"]);
    $member["pe_person_owner"] = phpaga_persons_formatname($member["pe_lastname_owner"], $member["pe_firstname_owner"]);

    return $member;
}


/**
 * Checks whether a person is a member of a the secified project
 *
 * @param int   $prj_id     Project ID
 * @param int   $pe_id      Person-ID
 *
 * @return bool $ismember   TRUE if the person is a member of the project.
 *                          FALSE if the person is not a member, or if an error occurs.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.3
 */

function phpaga_project_is_member($prj_id, $pe_id)
{

    if (!isset($prj_id) || !is_numeric($prj_id) || !isset($pe_id) || !is_numeric($pe_id))
        return false;

    $d = new PhPagaDbData;

    $n = $d->queryOneVal('SELECT COUNT(prj_id) FROM project_members WHERE prj_id = ? AND pe_id = ?',
                         array($prj_id, $pe_id));

    if (!PhPagaError::isError($n) && ($n > 0))
        return true;

    return false;
}


/**
 * Checks whether a person can view a specific project. This is true
 * is one of the following cases applies:
 *
 * - the user is the project's owner and has the permission to manage
 *   projects
 * - the user is the project's manager
 * - the user is a member of the project
 * - the user can view other people's projects
 * - the user can manage other people's projects
 *
 * @param int   $prj_id   Project ID
 * @param int   $pe_id    Person-ID (optional)
 *
 * @return bool $result   TRUE if the person can view the project.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.3
 */

function phpaga_project_canview($prj_id, $pe_id = 0)
{

    if (!isset($prj_id) || !is_numeric($prj_id) || !isset($pe_id) || !is_numeric($pe_id))
        return false;

    if ($pe_id == 0)
        $pe_id = $_SESSION["auth_user"]["pe_id"];

    $ismember = false;
    $isowner = false;
    $ismanager = false;

    $ismember = phpaga_project_is_member($prj_id, $pe_id);
    $isowner = phpaga_projects_isowner($prj_id, $pe_id);
    $ismanager = phpaga_projects_ismanager($prj_id, $pe_id);

    return($isowner && phpaga_user_hasperm(PHPAGA_PERM_MANAGE_PROJECTS)
           || $ismanager || $ismember
           || phpaga_user_hasperm(PHPAGA_PERM_VIEW_OTHERPROJECTS)
           || phpaga_user_hasperm(PHPAGA_PERM_MANAGE_OTHERPROJECTS));

}


/**
 * Checks whether a person can manage a specific project. This is true
 * is one of the following cases applies:
 *
 * - the user is the project's owner and has the permission to manage
 *   projects
 * - the user is the project's manager
 * - the user can manage other people's projects
 *
 * @param int   $prj_id   Project ID
 * @param int   $pe_id    Person-ID (optional)
 *
 * @return bool $result   TRUE if the person can manage the project.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.3
 */

function phpaga_project_canmanage($prj_id, $pe_id = 0)
{

    if (!isset($prj_id) || !is_numeric($prj_id) || !isset($pe_id) || !is_numeric($pe_id))
        return false;

    if ($pe_id == 0)
        $pe_id = $_SESSION["auth_user"]["pe_id"];

    $isowner = false;
    $ismanager = false;

    $isowner = phpaga_projects_isowner($prj_id, $pe_id);
    $ismanager = phpaga_projects_ismanager($prj_id, $pe_id);

    return($isowner && phpaga_user_hasperm(PHPAGA_PERM_MANAGE_PROJECTS)
           || $ismanager
           || phpaga_user_hasperm(PHPAGA_PERM_MANAGE_OTHERPROJECTS));

}


/**
 * Deletes a project member.
 *
 * @param  int     $prj_id      Project ID
 * @param  int     $pe_id       Person ID
 *
 * @return int     $result      A result code.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.2
 */

function phpaga_projects_deletemember($prj_id, $pe_id)
{
    if (!isset($prj_id) || !is_numeric($prj_id))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_PROJECT_INVALIDID);
    elseif (!isset($pe_id) || !is_numeric($pe_id))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_PERSON_INVALIDID);

    $d = new PhPagaDbData;

    $r = $d->beginTransaction();
    if (PhPagaError::isError($r))
        return $r;

    $r = $d->execute('DELETE FROM projects_hourlyrates WHERE prj_id = ? AND pe_id = ?',
                     array($prj_id, $pe_id));

    if (PhPagaError::isError($r)) {
        $d->rollback();
        return $r;
    }

    $r = $d->execute('DELETE FROM project_members WHERE prj_id = ? AND pe_id = ?',
                     array($prj_id, $pe_id));

    if (PhPagaError::isError($r)) {
        $d->rollback();
        return $r;
    }

    $r = $d->commit();

    return $r;
}


/**
 * Determine whether some person is the owner of a project. If pe_id
 * is omitted, the current user's pe_id is used.
 *
 * @param int    $prj_id    Project ID
 *
 * @param int    $pe_id     Person ID (optional, if omitted, the
 *                          current user's pe_id is used)

 * @param bool   $isowner   TRUE if pe_id is the owner of prj_id.
 *                          FALSE if pe_id is not the owner of prj_id, or if an error occurs.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.3
 */

function phpaga_projects_isowner($prj_id, $pe_id = null)
{

    $isowner = false;

    if (($pe_id == null) && isset($_SESSION['auth_user'])
        && isset($_SESSION['auth_user']['pe_id']) && is_numeric($_SESSION['auth_user']['pe_id']))

        $pe_id = $_SESSION['auth_user']['pe_id'];

    $d = new PhPagaDbData;

    $prjpe_id = $d->queryOneVal('SELECT pe_id FROM projects WHERE prj_id = ?', $prj_id);

    if (PhPagaError::isError($prjpe_id))
        return false;

    $isowner = ($prjpe_id == $pe_id);

    return $isowner;
}


/**
 * Determine whether some person is the manager of a project. If pe_id
 * is omitted, the current user's pe_id is used.
 *
 * @param int    $prj_id    Project ID
 *
 * @param int    $pe_id     Person ID (optional, if omitted, the
 *                          current user's pe_id is used)
 *
 * @param bool   $ismanager TRUE if pe_id is the manager of prj_id.
 *                          FALSE if pe_id is not the manager of prj_id, or if an error occurs.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.3
 */

function phpaga_projects_ismanager($prj_id, $pe_id = null)
{

    $ismanager = false;

    if (($pe_id == null) && isset($_SESSION['auth_user'])
        && isset($_SESSION['auth_user']['pe_id']) && is_numeric($_SESSION['auth_user']['pe_id']))

        $pe_id = $_SESSION['auth_user']['pe_id'];

    $d = new PhPagaDbData;

    $prjpe_id = $d->queryOneVal('SELECT prj_manager_pe_id FROM projects WHERE prj_id = ?', $prj_id);

    if (PhPagaError::isError($prjpe_id))
        return false;

    $ismanager = ($prjpe_id == $pe_id);

    return $ismanager;
}


/**
 * Get a list of projects a person is the owner or a member of.
 *
 * @param int    $pe_id     Person ID (optional, if omitted, the
 *                          current user's pe_id is used)
 *
 * @return array $persprj   Array containing the projects a person is
 *                          either the owner or a member of. A PhPagaError on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.3
 */

function phpaga_projects_getpersonprojects($pe_id = null)
{

    $persprj = array();
    $data = array();

    if (($pe_id == null) && isset($_SESSION['auth_user']) && isset($_SESSION['auth_user']['pe_id']) && is_numeric($_SESSION['auth_user']['pe_id']))
        $pe_id = $_SESSION['auth_user']['pe_id'];

    $d = new PhPagaDbData;
    $data = $d->queryAll('SELECT prj_id, prj_title FROM projects WHERE pe_id = ? ORDER by prj_title', $pe_id);

    if (PhPagaError::isError($data))
        return $data;

    foreach ($data as $dx)
        $persprj[$dx['prj_id']] = array('prj_id' => $dx['prj_id'],
                                        'prj_title' => $dx['prj_title'],
                                        'relation' => _('owner'));

    $data = array();

    $data = $d->queryAll('SELECT p.prj_id, prj_title FROM projects p
                        LEFT JOIN project_members m ON m.prj_id = p.prj_id
                        WHERE m.pe_id = ?
                        ORDER by prj_title', $pe_id);

    if (PhPagaError::isError($data))
        return $data;

    foreach ($data as $dx)
        if (isset($persprj[$dx['prj_id']]))
            $persprj[$dx['prj_id']]['relation'] .= ', '._('member');
        else
            $persprj[$dx['prj_id']] = array('prj_id' => $dx['prj_id'],
                                            'prj_title' => $dx['prj_title'],
                                            'relation' => _('member'));

    return $persprj;
}


/**
 * Get the highest project ID.
 *
 * @return int   $prj_id    Project ID. A PhPagaErrro object on failure.
 *
 * @author Mark Parssey <markpa@users.sourceforge.net>
 * @since phpaga 0.3
 */

function phpaga_projects_getLastProject()
{

    $d = new PhPagaDbData;

    $prj_id = $d->queryOneVal('SELECT max(prj_id) as prj_id from projects');

    return $prj_id;
}


/**
 * Sets a list of project priorities and returns them as the content
 * of an array.
 *
 * @return array $prjpriorityList  Array of project priorities
 *
 * @author Mark Parssey <markpa@users.sourceforge.net>
 * @since phpaga 0.3
 */

function phpaga_projects_getPriorityList()
{
    $prjpriorityList = array(1 => 1,2,3,4,5,6,7,8,9);

    phpaga_arrayAddOption($prjpriorityList, PHPAGA_OPTION_NONE);

    return $prjpriorityList;
}


/**
 * Returns the sort order for the various fields that are displayed in
 * the projects list (required for the templates). of an array.
 *
 * @param  int    $order  Selected 'order by'
 *
 * @return array  $sort   Sort order
 *
 * @author Mark Parssey <markpa@users.sourceforge.net>
 * @since phpaga 0.3
 */

function phpaga_projects_getSortOrder($order)
{

    $sort = array();

    switch ($order) {

        case ORDER_PRJ_PRJSTATUS_ASC:
            $sort['pr'] = ORDER_PRJ_PR_DESC;
            $sort['prjstat'] = ORDER_PRJ_PRJSTATUS_DESC;
            $sort['cpn'] = ORDER_PRJ_CUSTOMER_ASC;
            $sort['start'] = ORDER_PRJ_STARTDATE_ASC;
            $sort['end'] = ORDER_PRJ_PROJENDDATE_ASC;
            $sort['deadline'] = ORDER_PRJ_PROJDEADLINE_ASC;
            $sort['prjcat'] = ORDER_PRJ_PRJCAT_ASC;
            $sort['duration'] = ORDER_PRJ_DURATION_ASC;
            $sort['prio'] = ORDER_PRJ_PRIORITY_ASC;
            break;

        case ORDER_PRJ_CUSTOMER_ASC:
            $sort['pr'] = ORDER_PRJ_PR_ASC;
            $sort['prjstat'] = ORDER_PRJ_PRJSTATUS_ASC;
            $sort['cpn'] = ORDER_PRJ_CUSTOMER_DESC;
            $sort['start'] = ORDER_PRJ_STARTDATE_ASC;
            $sort['end'] = ORDER_PRJ_PROJENDDATE_ASC;
            $sort['deadline'] = ORDER_PRJ_PROJDEADLINE_ASC;
            $sort['prjcat'] = ORDER_PRJ_PRJCAT_ASC;
            $sort['duration'] = ORDER_PRJ_DURATION_ASC;
            $sort['prio'] = ORDER_PRJ_PRIORITY_ASC;
            break;

        case ORDER_PRJ_STARTDATE_ASC:
            $sort['pr'] = ORDER_PRJ_PR_ASC;
            $sort['prjstat'] = ORDER_PRJ_PRJSTATUS_ASC;
            $sort['cpn'] = ORDER_PRJ_CUSTOMER_ASC;
            $sort['start'] = ORDER_PRJ_STARTDATE_DESC;
            $sort['end'] = ORDER_PRJ_PROJENDDATE_ASC;
            $sort['deadline'] = ORDER_PRJ_PROJDEADLINE_ASC;
            $sort['prjcat'] = ORDER_PRJ_PRJCAT_ASC;
            $sort['duration'] = ORDER_PRJ_DURATION_ASC;
            $sort['prio'] = ORDER_PRJ_PRIORITY_ASC;
            break;

        case ORDER_PRJ_PROJENDDATE_ASC:
            $sort['pr'] = ORDER_PRJ_PR_ASC;
            $sort['prjstat'] = ORDER_PRJ_PRJSTATUS_ASC;
            $sort['cpn'] = ORDER_PRJ_CUSTOMER_ASC;
            $sort['start'] = ORDER_PRJ_STARTDATE_ASC;
            $sort['end'] = ORDER_PRJ_PROJENDDATE_DESC;
            $sort['deadline'] = ORDER_PRJ_PROJDEADLINE_ASC;
            $sort['prjcat'] = ORDER_PRJ_PRJCAT_ASC;
            $sort['duration'] = ORDER_PRJ_DURATION_ASC;
            $sort['prio'] = ORDER_PRJ_PRIORITY_ASC;
            break;

        case ORDER_PRJ_PROJDEADLINE_ASC:
            $sort['pr'] = ORDER_PRJ_PR_ASC;
            $sort['prjstat'] = ORDER_PRJ_PRJSTATUS_ASC;
            $sort['cpn'] = ORDER_PRJ_CUSTOMER_ASC;
            $sort['start'] = ORDER_PRJ_STARTDATE_ASC;
            $sort['end'] = ORDER_PRJ_PROJENDDATE_ASC;
            $sort['deadline'] = ORDER_PRJ_PROJDEADLINE_DESC;
            $sort['prjcat'] = ORDER_PRJ_PRJCAT_ASC;
            $sort['duration'] = ORDER_PRJ_DURATION_ASC;
            $sort['prio'] = ORDER_PRJ_PRIORITY_ASC;
            break;

        case ORDER_PRJ_PRJCAT_ASC:
            $sort['pr'] = ORDER_PRJ_PR_ASC;
            $sort['prjstat'] = ORDER_PRJ_PRJSTATUS_ASC;
            $sort['cpn'] = ORDER_PRJ_CUSTOMER_ASC;
            $sort['start'] = ORDER_PRJ_STARTDATE_ASC;
            $sort['end'] = ORDER_PRJ_PROJENDDATE_ASC;
            $sort['deadline'] = ORDER_PRJ_PROJDEADLINE_ASC;
            $sort['prjcat'] = ORDER_PRJ_PRJCAT_DESC;
            $sort['duration'] = ORDER_PRJ_DURATION_ASC;
            $sort['prio'] = ORDER_PRJ_PRIORITY_ASC;
            break;

        case ORDER_PRJ_DURATION_ASC:
            $sort['pr'] = ORDER_PRJ_PR_ASC;
            $sort['prjstat'] = ORDER_PRJ_PRJSTATUS_ASC;
            $sort['cpn'] = ORDER_PRJ_CUSTOMER_ASC;
            $sort['start'] = ORDER_PRJ_STARTDATE_ASC;
            $sort['end'] = ORDER_PRJ_PROJENDDATE_ASC;
            $sort['deadline'] = ORDER_PRJ_PROJDEADLINE_ASC;
            $sort['prjcat'] = ORDER_PRJ_PRJCAT_ASC;
            $sort['duration'] = ORDER_PRJ_DURATION_DESC;
            $sort['prio'] = ORDER_PRJ_PRIORITY_ASC;
            break;

        case ORDER_PRJ_PR_ASC:
            $sort['pr'] = ORDER_PRJ_PR_DESC;
            $sort['prjstat'] = ORDER_PRJ_PRJSTATUS_ASC;
            $sort['cpn'] = ORDER_PRJ_CUSTOMER_ASC;
            $sort['start'] = ORDER_PRJ_STARTDATE_ASC;
            $sort['end'] = ORDER_PRJ_PROJENDDATE_ASC;
            $sort['deadline'] = ORDER_PRJ_PROJDEADLINE_ASC;
            $sort['prjcat'] = ORDER_PRJ_PRJCAT_ASC;
            $sort['duration'] = ORDER_PRJ_DURATION_ASC;
            $sort['prio'] = ORDER_PRJ_PRIORITY_ASC;
            break;

        case ORDER_PRJ_PRIORITY_ASC:
            $sort['pr'] = ORDER_PRJ_PR_DESC;
            $sort['prjstat'] = ORDER_PRJ_PRJSTATUS_ASC;
            $sort['cpn'] = ORDER_PRJ_CUSTOMER_ASC;
            $sort['start'] = ORDER_PRJ_STARTDATE_ASC;
            $sort['end'] = ORDER_PRJ_PROJENDDATE_ASC;
            $sort['deadline'] = ORDER_PRJ_PROJDEADLINE_ASC;
            $sort['prjcat'] = ORDER_PRJ_PRJCAT_ASC;
            $sort['duration'] = ORDER_PRJ_DURATION_ASC;
            $sort['prio'] = ORDER_PRJ_PRIORITY_DESC;
            break;

        default:
            $sort['pr'] = ORDER_PRJ_PR_ASC;
            $sort['prjstat'] = ORDER_PRJ_PRJSTATUS_ASC;
            $sort['cpn'] = ORDER_PRJ_CUSTOMER_ASC;
            $sort['start'] = ORDER_PRJ_STARTDATE_ASC;
            $sort['end'] = ORDER_PRJ_PROJENDDATE_ASC;
            $sort['deadline'] = ORDER_PRJ_PROJDEADLINE_ASC;
            $sort['prjcat'] = ORDER_PRJ_PRJCAT_ASC;
            $sort['duration'] = ORDER_PRJ_DURATION_ASC;
            $sort['prio'] = ORDER_PRJ_PRIORITY_ASC;
            break;
    }

    return $sort;
}


/**
 * Sets the default hourly rates per task type for a given project. If
 * the person ID is set, the settings for that particular person and
 * project are stored.
 *
 * @param  int    $prj_id  Project ID
 * @param  array  $opcats  Array with hourly rates per task type
 * @param  int    $pe_id   Person ID (optional)
 *
 * @return bool   $result  True. A PhPagaError on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.3
 */

function phpaga_projects_sethourlyrates($prj_id, $opcats, $pe_id = null)
{

    if (!isset($prj_id) || !is_numeric($prj_id))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_PROJECT_INVALIDID);

    if (!isset($opcats) || !is_array($opcats))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_INVALID_PARAMTYPE);

    if (isset($pe_id) && strlen($pe_id) && !is_numeric($pe_id))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_INVALID_PARAMTYPE);

    $d = new PhPagaDbData;

    $r = $d->beginTransaction();
    if (PhPagaError::isError($r))
        return $r;

    if ($pe_id == null)
        $r = $d->execute('DELETE FROM projects_hourlyrates WHERE pe_id IS NULL AND prj_id = ? ', $prj_id);
    else
        $r = $d->execute('DELETE FROM projects_hourlyrates WHERE pe_id = ? AND prj_id = ?', array($pe_id, $prj_id));

    if (PhPagaError::isError($r)) {
        $d->rollback();
        return $r;
    }

    $fields = array('phr_id',
                    'pe_id_lastmod',
                    'prj_id',
                    'pe_id',
                    'opcat_id',
                    'phr_hourlyrate');

    $val = array();

    foreach($opcats as $opcat) {
        if (isset($opcat['opcat_id']) && is_numeric($opcat['opcat_id'])
            && isset($opcat['opcat_hourlyrate']) && is_numeric($opcat['opcat_hourlyrate'])) {

            $val[] = array($d->nextId('projects_hourlyrates_phr_id'),
                           $_SESSION['auth_user']['pe_id'],
                           $prj_id,
                           $pe_id,
                           $opcat['opcat_id'],
                           $opcat['opcat_hourlyrate']);
        }
    }

    if (count($val)) {
        $r = $d->insertMultiple('projects_hourlyrates', $fields, $val);

        if (PhPagaError::isError($r))
            return $r;
    }

    $r = $d->commit();

    return $r;
}


/**
 * Retrieves the default hourly rates per task type for a given
 * project. If task type is not set on project level, its title is
 * fetched from the task type list and the default rate is presumed to
 * be null. If the person ID is set, the settings for that particular
 * person and project are fetched.
 *
 * @param  int    $prj_id  Project ID
 * @param  int    $pe_id   Person ID (optional)
 *
 * @param  array  $opcats Array with hourly rates per task type. A PhPagaError object on failure.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @since phpaga 0.3
 */

function phpaga_projects_gethourlyrates($prj_id, $pe_id = null)
{

    $opcats = array();
    $qparam = array();

    if (!isset($prj_id) || !is_numeric($prj_id))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_PROJECT_INVALIDID);

    if (isset($pe_id) && strlen($pe_id) && !is_numeric($pe_id))
        return PhPagaError::raiseErrorByCode(PHPAGA_ERR_INVALID_PARAMTYPE);

    $d = new PhPagaDbData;

    if ($pe_id == null) {

        $query = '(SELECT p.opcat_id, opcat_title, opcat_color, p.phr_hourlyrate as opcat_hourlyrate
            FROM projects_hourlyrates p
            LEFT JOIN operationcat o ON o.opcat_id = p.opcat_id
            WHERE p.pe_id IS NULL AND p.prj_id = ?)
            UNION (SELECT opcat_id, opcat_title, opcat_color, NULL as opcat_hourlyrate
            FROM operationcat
            WHERE opcat_id NOT IN
            (SELECT opcat_id FROM projects_hourlyrates WHERE pe_id IS NULL
            AND prj_id = ?)) order by opcat_title ';

        $qparam = array($prj_id, $prj_id);

    }  else {

        $query = '(SELECT p.opcat_id, opcat_title, opcat_color, p.phr_hourlyrate as opcat_hourlyrate
            FROM projects_hourlyrates p
            LEFT JOIN operationcat o ON o.opcat_id = p.opcat_id
            WHERE p.pe_id = ? AND p.prj_id = ?)
            UNION (SELECT opcat_id, opcat_title, opcat_color, NULL as opcat_hourlyrate
            FROM operationcat
            WHERE opcat_id NOT IN
            (SELECT opcat_id FROM projects_hourlyrates WHERE pe_id = ?
            AND prj_id = ?)) order by opcat_title ';

        $qparam = array($pe_id, $prj_id, $pe_id, $prj_id);
    }

    $opcats = $d->queryAll($query, $qparam);

    return $opcats;
}

?>