Location: PHPKode > projects > ETraxis > etraxis-2.1.1/src/dbo/fields.php
<?php

/**
 * Fields
 *
 * This module provides API to work with eTraxis fields.
 * See also {@link http://www.etraxis.org/docs-schema.php#tbl_fields tbl_fields} database table.
 *
 * @package DBO
 * @subpackage Fields
 */

//--------------------------------------------------------------------------------------------------
//
//  eTraxis - Records tracking web-based system.
//  Copyright (C) 2005-2009 by Artem Rodygin
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License along
//  with this program; if not, write to the Free Software Foundation, Inc.,
//  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
//--------------------------------------------------------------------------------------------------
//  Author                  Date            Description of modifications
//--------------------------------------------------------------------------------------------------
//  Artem Rodygin           2005-03-23      new-001: Records tracking web-based system should be implemented.
//  Artem Rodygin           2005-08-06      new-021: Default permissions for new fields.
//  Artem Rodygin           2005-08-23      bug-050: Removable field will not be removed in some cases.
//  Artem Rodygin           2005-08-23      new-053: All the calls of DAL API functions should be moved to DBO API.
//  Artem Rodygin           2005-08-27      bug-062: List values are not set.
//  Artem Rodygin           2005-08-27      bug-064: New field cannot be created if some existed field has been deleted before.
//  Artem Rodygin           2005-09-01      bug-079: String database columns are not enough to store UTF-8 values.
//  Artem Rodygin           2005-09-07      new-102: Increase maximum length of comments and 'multilined text' fields up to 4000 characters.
//  Artem Rodygin           2005-09-07      new-100: 'Date' field type should be implemented.
//  Artem Rodygin           2005-09-08      new-101: 'Duration' field type should be implemented.
//  Artem Rodygin           2005-09-12      new-105: Format of date values are being entered should depend on user locale settings.
//  Artem Rodygin           2005-09-27      new-141: Source code review.
//  Artem Rodygin           2005-10-22      bug-163: Some filters are malfunctional.
//  Artem Rodygin           2005-11-27      bug-179: Maximum valid date cannot be entered.
//  Artem Rodygin           2006-03-16      new-175: Implement user roles in permissions.
//  Artem Rodygin           2006-04-21      bug-243: Unexpected message "Field with entered name already exists".
//  Artem Rodygin           2006-04-21      new-247: The 'responsible' user role should be obliterated.
//  Artem Rodygin           2006-05-23      bug-262: PHP Warning: ocifetchinto(): OCILobRead: ORA-24806: LOB form mismatch
//  Artem Rodygin           2006-06-25      bug-269: Multilined text values are cut to 1000 characters.
//  Artem Rodygin           2006-07-23      new-296: Changing fields order.
//  Artem Rodygin           2006-10-12      new-137: Custom queries.
//  Artem Rodygin           2006-11-04      new-364: Default fields values.
//  Artem Rodygin           2006-11-20      new-377: Custom views.
//  Artem Rodygin           2006-12-06      bug-421: 1/1/1970 cannot be set as minimum value of date field.
//  Artem Rodygin           2006-12-10      new-422: Increase maximum length of string fields.
//  Artem Rodygin           2007-01-05      new-491: [SF1647212] Group-wide transition permission.
//  Artem Rodygin           2007-02-25      bug-497: Cannot postpone record till tomorrow.
//  Artem Rodygin           2007-04-04      bug-515: Wrong dates in note/alert when new date field is being created.
//  Artem Rodygin           2007-09-09      new-563: Custom separators inside fields set.
//  Yury Udovichenko        2007-11-14      new-548: Custom links in text fields.
//  Artem Rodygin           2007-11-14      bug-626: eTraxis Error: [dal_execute] dbx_error(): Table 'etraxis.tbl_columns' doesn't exist
//  Artem Rodygin           2007-11-27      new-633: The 'dbx' extension should not be used.
//  Artem Rodygin           2008-01-28      new-531: LDAP Guest users
//  Artem Rodygin           2008-02-03      new-601: [SF1814666] Export and Import Templates
//  Artem Rodygin           2008-02-08      bug-673: Newly created field always has empty strings as its regexps instead of NULL.
//  Artem Rodygin           2008-02-08      new-671: Default value for 'date' fields should be relative.
//  Artem Rodygin           2008-03-20      bug-687: "XML parser error" on template import, if zero is specified in 'critical_age' template's parameter.
//  Artem Rodygin           2008-04-09      bug-697: XML import fails on date values.
//  Artem Rodygin           2008-04-13      bug-698: XML import // All new line characters are lost in default value of multilined field.
//  Artem Rodygin           2008-04-20      new-703: Separated permissions set for current responsible.
//  Artem Rodygin           2008-04-30      bug-699: Views // Names of custom columns are duplicated in the list of available columns, when there are two fields of different types with the same name.
//  Artem Rodygin           2008-09-10      new-716: 'Today' value in date field range.
//  Artem Rodygin           2008-11-10      new-749: Guest access for unauthorized users.
//  Artem Rodygin           2009-01-08      new-774: 'Anyone' system role permissions.
//  Artem Rodygin           2009-01-15      bug-787: [SF2509057] Can't import template
//  Artem Rodygin           2009-03-24      bug-803: "XML parser error" on import of preliminary exported template.
//  Artem Rodygin           2009-04-24      new-817: Field permissions dialog refactoring.
//  Artem Rodygin           2009-04-25      new-801: Range of valid date values must be related to current date.
//  Artem Rodygin           2009-06-12      new-824: PHP 4 is discontinued.
//  Artem Rodygin           2009-06-17      bug-825: Database gets empty strings instead of NULL values.
//  Artem Rodygin           2009-09-09      new-826: Native unicode support for Microsoft SQL Server.
//  Artem Rodygin           2009-10-17      new-802: [SF2704057] possibility to disable fields
//--------------------------------------------------------------------------------------------------

/**#@+
 * Dependency.
 */
require_once('../engine/engine.php');
require_once('../dbo/values.php');
/**#@-*/

//--------------------------------------------------------------------------------------------------
//  Definitions.
//--------------------------------------------------------------------------------------------------

/**#@+
 * Data restriction.
 */
define('MAX_FIELD_NAME',       50);
define('MAX_FIELD_INTEGER',    1000000000);
define('MAX_FIELD_STRING',     250);
define('MAX_FIELD_MULTILINED', 4000);
define('MAX_FIELD_LIST_ITEMS', 1000);
define('MAX_LISTITEM_NAME',    50);
define('MIN_FIELD_DATE',       ~MAXINT);
define('MAX_FIELD_DATE',       MAXINT);
define('MIN_FIELD_DURATION',   0);
define('MAX_FIELD_DURATION',   59999999);
define('MAX_FIELD_REGEX',      1000);
/**#@-*/

/**
 * Unix Epoch of 1977-12-29.
 * Needed to evaluate maximum length of string with date, formatted in current user's locale.
 */
define('SAMPLE_DATE', mktime(0,0,0,12,29,1977));

/**#@+
 * Field type.
 */
define('FIELD_TYPE_MINIMUM',    1);
define('FIELD_TYPE_NUMBER',     1);
define('FIELD_TYPE_STRING',     2);
define('FIELD_TYPE_MULTILINED', 3);
define('FIELD_TYPE_CHECKBOX',   4);
define('FIELD_TYPE_LIST',       5);
define('FIELD_TYPE_RECORD',     6);
define('FIELD_TYPE_DATE',       7);
define('FIELD_TYPE_DURATION',   8);
define('FIELD_TYPE_MAXIMUM',    8);
/**#@-*/

/**#@+
 * Field permission.
 */
define('FIELD_RESTRICTED',     0);  // no permissions
define('FIELD_ALLOW_TO_READ',  1);  // read-only permissions
define('FIELD_ALLOW_TO_WRITE', 2);  // read and write permissions
/**#@-*/

/**#@+
 * Field role.
 */
define('FIELD_ROLE_AUTHOR',      -1);
define('FIELD_ROLE_RESPONSIBLE', -2);
define('FIELD_ROLE_REGISTERED',  -3);
define('MIN_FIELD_ROLE', FIELD_ROLE_REGISTERED);
/**#@-*/

// Field type resources.
$field_type_res = array
(
    FIELD_TYPE_NUMBER     => RES_NUMBER_ID,
    FIELD_TYPE_STRING     => RES_STRING_ID,
    FIELD_TYPE_MULTILINED => RES_MULTILINED_TEXT_ID,
    FIELD_TYPE_CHECKBOX   => RES_CHECKBOX_ID,
    FIELD_TYPE_LIST       => RES_LIST_ID,
    FIELD_TYPE_RECORD     => RES_RECORD_ID,
    FIELD_TYPE_DATE       => RES_DATE_ID,
    FIELD_TYPE_DURATION   => RES_DURATION_ID,
);

//--------------------------------------------------------------------------------------------------
//  Functions.
//--------------------------------------------------------------------------------------------------

/**
 * Finds in database and returns the information about specified field.
 *
 * @param int $id {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_id Field ID}.
 * @return array Array with data if field is found in database, FALSE otherwise.
 */
function field_find ($id)
{
    debug_write_log(DEBUG_TRACE, '[field_find]');
    debug_write_log(DEBUG_DUMP,  '[field_find] $id = ' . $id);

    $rs = dal_query('fields/fndid.sql', $id);

    return ($rs->rows == 0 ? FALSE : $rs->fetch());
}

/**
 * Returns {@link CRecordset DAL recordset} which contains all existing fields of specified state,
 * sorted in accordance with current sort mode.
 *
 * @param int $id {@link http://www.etraxis.org/docs-schema.php#tbl_states_state_id State ID}.
 * @param int &$sort Sort mode (used as output only). The function retrieves current sort mode from
 * client cookie ({@link COOKIE_FIELDS_SORT}) and updates it, if it's out of valid range.
 * @param int &$page Number of current page tab (used as output only). The function retrieves current
 * page from client cookie ({@link COOKIE_FIELDS_PAGE}) and updates it, if it's out of valid range.
 * @return CRecordset Recordset with list of fields.
 */
function field_list ($id, &$sort, &$page)
{
    debug_write_log(DEBUG_TRACE, '[field_list]');
    debug_write_log(DEBUG_DUMP,  '[field_list] $id = ' . $id);

    $sort_modes = array
    (
        1 => 'field_order asc',
        2 => 'field_name asc',
        3 => 'field_type asc, field_name asc',
        4 => 'is_required asc, field_name asc',
        5 => 'field_order desc',
        6 => 'field_name desc',
        7 => 'field_type desc, field_name desc',
        8 => 'is_required desc, field_name desc',
    );

    $sort = try_request('sort', try_cookie(COOKIE_FIELDS_SORT, 1));
    $sort = ustr2int($sort, 1, count($sort_modes));

    $page = try_request('page', try_cookie(COOKIE_FIELDS_PAGE));
    $page = ustr2int($page, 1, MAXINT);

    save_cookie(COOKIE_FIELDS_SORT, $sort);
    save_cookie(COOKIE_FIELDS_PAGE, $page);

    return dal_query('fields/list.sql', $id, $sort_modes[$sort]);
}

/**
 * Returns number of fields for specified state.
 *
 * @param int $id {@link http://www.etraxis.org/docs-schema.php#tbl_states_state_id State ID}.
 * @return int Current number of fields.
 */
function field_count ($id)
{
    debug_write_log(DEBUG_TRACE, '[field_count]');
    debug_write_log(DEBUG_DUMP,  '[field_count] $id = ' . $id);

    $rs = dal_query('fields/count.sql', $id);

    return ($rs->rows == 0 ? 0 : $rs->fetch(0));
}

/**
 * Returns list of all local and global groups which have specified permission for specified field.
 *
 * @param int $pid {@link http://www.etraxis.org/docs-schema.php#tbl_projects_project_id Project ID}.
 * @param int $fid {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_id Field ID}.
 * @param int $perms Permission:
 * <ul>
 * <li>{@link FIELD_ALLOW_TO_READ}</li>
 * <li>{@link FIELD_ALLOW_TO_WRITE}</li>
 * </ul>
 * @return CRecordset Recordset with list of groups.
 */
function field_amongs ($pid, $fid, $perms)
{
    debug_write_log(DEBUG_TRACE, '[field_amongs]');
    debug_write_log(DEBUG_DUMP,  '[field_amongs] $pid   = ' . $pid);
    debug_write_log(DEBUG_DUMP,  '[field_amongs] $fid   = ' . $fid);
    debug_write_log(DEBUG_DUMP,  '[field_amongs] $perms = ' . $perms);

    return dal_query('fields/fpamongs.sql', $pid, $fid, $perms);
}

/**
 * Returns list of all local and global groups which don't have specified permission for specified field.
 *
 * @param int $pid {@link http://www.etraxis.org/docs-schema.php#tbl_projects_project_id Project ID}.
 * @param int $fid {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_id Field ID}.
 * @param int $perms Permission:
 * <ul>
 * <li>{@link FIELD_ALLOW_TO_READ}</li>
 * <li>{@link FIELD_ALLOW_TO_WRITE}</li>
 * </ul>
 * @return CRecordset Recordset with list of groups.
 */
function field_others ($pid, $fid, $perms)
{
    debug_write_log(DEBUG_TRACE, '[field_others]');
    debug_write_log(DEBUG_DUMP,  '[field_others] $pid   = ' . $pid);
    debug_write_log(DEBUG_DUMP,  '[field_others] $fid   = ' . $fid);
    debug_write_log(DEBUG_DUMP,  '[field_others] $perms = ' . $perms);

    return dal_query('fields/fpothers.sql', $pid, $fid, $perms);
}

/**
 * Validates general field information before creation or modification.
 *
 * @param string $field_name {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_name Field name}.
 * @return int Error code:
 * <ul>
 * <li>{@link NO_ERROR} - data are valid</li>
 * <li>{@link ERROR_INCOMPLETE_FORM} - at least one of required field is empty</li>
 * </ul>
 */
function field_validate ($field_name)
{
    debug_write_log(DEBUG_TRACE, '[field_validate]');
    debug_write_log(DEBUG_DUMP,  '[field_validate] $field_name = ' . $field_name);

    if (ustrlen($field_name) == 0)
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate] At least one required field is empty.');
        return ERROR_INCOMPLETE_FORM;
    }

    return NO_ERROR;
}

/**
 * Validates 'Number' field information before creation or modification.
 *
 * @param string $field_name {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_name Field name}.
 * @param int $min_value Minimum allowed value of the field.
 * @param int $max_value Maximum allowed value of the field.
 * @param int $def_value Default allowed value of the field (NULL by default).
 * @return int Error code:
 * <ul>
 * <li>{@link NO_ERROR} - data are valid</li>
 * <li>{@link ERROR_INCOMPLETE_FORM} - at least one of required field is empty</li>
 * <li>{@link ERROR_INVALID_INTEGER_VALUE} - at least one of specified integer values is invalid</li>
 * <li>{@link ERROR_INTEGER_VALUE_OUT_OF_RANGE} - at least one of specified integer values is less than -{@link MAX_FIELD_INTEGER}, or greater than {@link MAX_FIELD_INTEGER}</li>
 * <li>{@link ERROR_MIN_MAX_VALUES} - maximum value is less than minimum one</li>
 * <li>{@link ERROR_DEFAULT_VALUE_OUT_OF_RANGE} - default value is less than $min_value, or greater than $max_value</li>
 * </ul>
 */
function field_validate_number ($field_name, $min_value, $max_value, $def_value = NULL)
{
    debug_write_log(DEBUG_TRACE, '[field_validate_number]');
    debug_write_log(DEBUG_DUMP,  '[field_validate_number] $field_name = ' . $field_name);
    debug_write_log(DEBUG_DUMP,  '[field_validate_number] $min_value  = ' . $min_value);
    debug_write_log(DEBUG_DUMP,  '[field_validate_number] $max_value  = ' . $max_value);
    debug_write_log(DEBUG_DUMP,  '[field_validate_number] $def_value  = ' . $def_value);

    // Check that field name is not empty.
    if (ustrlen($field_name) == 0)
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_number] At least one required field is empty.');
        return ERROR_INCOMPLETE_FORM;
    }

    // Check that specified values are integer.
    if (!is_intvalue($min_value) ||
        !is_intvalue($max_value) ||
        (!is_null($def_value) && !is_intvalue($def_value)))
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_number] Invalid integer value.');
        return ERROR_INVALID_INTEGER_VALUE;
    }

    // Check that specified values are in the range of valid values.
    if ($min_value < -MAX_FIELD_INTEGER || $min_value > MAX_FIELD_INTEGER ||
        $max_value < -MAX_FIELD_INTEGER || $max_value > MAX_FIELD_INTEGER)
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_number] Integer value is out of range.');
        return ERROR_INTEGER_VALUE_OUT_OF_RANGE;
    }

    // Check that minimum value is less than maximum one.
    if ($min_value >= $max_value)
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_number] Minimum value is greater then maximum one.');
        return ERROR_MIN_MAX_VALUES;
    }

    // Check that default value is in the range between minimum and maximum ones.
    if (!is_null($def_value) &&
        ($def_value < $min_value || $def_value > $max_value))
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_number] Default value is out of range.');
        return ERROR_DEFAULT_VALUE_OUT_OF_RANGE;
    }

    return NO_ERROR;
}

/**
 * Validates 'String' field information before creation or modification.
 *
 * @param string $field_name {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_name Field name}.
 * @param int $max_length Maximum allowed length of string value in this field.
 * @return int Error code:
 * <ul>
 * <li>{@link NO_ERROR} - data are valid</li>
 * <li>{@link ERROR_INCOMPLETE_FORM} - at least one of required field is empty</li>
 * <li>{@link ERROR_INVALID_INTEGER_VALUE} - specified maximum length is invalid</li>
 * <li>{@link ERROR_INTEGER_VALUE_OUT_OF_RANGE} - specified maximum length is greater than {@link MAX_FIELD_STRING}</li>
 * </ul>
 */
function field_validate_string ($field_name, $max_length)
{
    debug_write_log(DEBUG_TRACE, '[field_validate_string]');
    debug_write_log(DEBUG_DUMP,  '[field_validate_string] $field_name = ' . $field_name);
    debug_write_log(DEBUG_DUMP,  '[field_validate_string] $max_length = ' . $max_length);

    // Check that field name is not empty.
    if (ustrlen($field_name) == 0)
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_string] At least one required field is empty.');
        return ERROR_INCOMPLETE_FORM;
    }

    // Check that specified values are integer.
    if (!is_intvalue($max_length))
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_string] Invalid integer value.');
        return ERROR_INVALID_INTEGER_VALUE;
    }

    // Check that specified values are in the range of valid values.
    if ($max_length < 1 || $max_length > MAX_FIELD_STRING)
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_string] Integer value is out of range.');
        return ERROR_INTEGER_VALUE_OUT_OF_RANGE;
    }

    return NO_ERROR;
}

/**
 * Validates 'Multilined text' field information before creation or modification.
 *
 * @param string $field_name {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_name Field name}.
 * @param int $max_length Maximum allowed length of string value in this field.
 * @return int Error code:
 * <ul>
 * <li>{@link NO_ERROR} - data are valid</li>
 * <li>{@link ERROR_INCOMPLETE_FORM} - at least one of required field is empty</li>
 * <li>{@link ERROR_INVALID_INTEGER_VALUE} - specified maximum length is invalid</li>
 * <li>{@link ERROR_INTEGER_VALUE_OUT_OF_RANGE} - specified maximum length is greater than {@link MAX_FIELD_MULTILINED}</li>
 * </ul>
 */
function field_validate_multilined ($field_name, $max_length)
{
    debug_write_log(DEBUG_TRACE, '[field_validate_multilined]');
    debug_write_log(DEBUG_DUMP,  '[field_validate_multilined] $field_name = ' . $field_name);
    debug_write_log(DEBUG_DUMP,  '[field_validate_multilined] $max_length = ' . $max_length);

    // Check that field name is not empty.
    if (ustrlen($field_name) == 0)
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_multilined] At least one required field is empty.');
        return ERROR_INCOMPLETE_FORM;
    }

    // Check that specified values are integer.
    if (!is_intvalue($max_length))
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_multilined] Invalid integer value.');
        return ERROR_INVALID_INTEGER_VALUE;
    }

    // Check that specified values are in the range of valid values.
    if ($max_length < 1 || $max_length > MAX_FIELD_MULTILINED)
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_multilined] Integer value is out of range.');
        return ERROR_INTEGER_VALUE_OUT_OF_RANGE;
    }

    return NO_ERROR;
}

/**
 * Validates 'Date' field information before creation or modification.
 *
 * @param string $field_name {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_name Field name}.
 * @param int $min_value Minimum allowed value of the field.
 * @param int $max_value Maximum allowed value of the field.
 * @param int $def_value Default allowed value of the field (NULL by default).
 * @return int Error code:
 * <ul>
 * <li>{@link NO_ERROR} - data are valid</li>
 * <li>{@link ERROR_INCOMPLETE_FORM} - at least one of required field is empty</li>
 * <li>{@link ERROR_INVALID_DATE_VALUE} - at least one of specified dates is invalid integer value</li>
 * <li>{@link ERROR_DATE_VALUE_OUT_OF_RANGE} - at least one of specified date values is less than {@link MIN_FIELD_DATE}, or greater than {@link MAX_FIELD_DATE}</li>
 * <li>{@link ERROR_MIN_MAX_VALUES} - maximum value is less than minimum one</li>
 * <li>{@link ERROR_DEFAULT_VALUE_OUT_OF_RANGE} - default value is less than $min_value, or greater than $max_value</li>
 * </ul>
 */
function field_validate_date ($field_name, $min_value, $max_value, $def_value = NULL)
{
    debug_write_log(DEBUG_TRACE, '[field_validate_date]');
    debug_write_log(DEBUG_DUMP,  '[field_validate_date] $field_name = ' . $field_name);
    debug_write_log(DEBUG_DUMP,  '[field_validate_date] $min_value  = ' . $min_value);
    debug_write_log(DEBUG_DUMP,  '[field_validate_date] $max_value  = ' . $max_value);
    debug_write_log(DEBUG_DUMP,  '[field_validate_date] $def_value  = ' . $def_value);

    // Check that field name and specified values are not empty.
    if (ustrlen($field_name) == 0 ||
        ustrlen($min_value)  == 0 ||
        ustrlen($max_value)  == 0)
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_date] At least one required field is empty.');
        return ERROR_INCOMPLETE_FORM;
    }

    // Check that specified values are integer.
    if (!is_intvalue($min_value) ||
        !is_intvalue($max_value) ||
        (!is_null($def_value) && !is_intvalue($def_value)))
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_date] Invalid integer value.');
        return ERROR_INVALID_DATE_VALUE;
    }

    // Check that specified values are in the range of valid values.
    if ($min_value < MIN_FIELD_DATE || $min_value > MAX_FIELD_DATE ||
        $max_value < MIN_FIELD_DATE || $max_value > MAX_FIELD_DATE)
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_date] Integer value is out of range.');
        return ERROR_DATE_VALUE_OUT_OF_RANGE;
    }

    // Check that minimum value is less than maximum one.
    if ($min_value >= $max_value)
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_date] Minimum value is greater then maximum one.');
        return ERROR_MIN_MAX_VALUES;
    }

    // Check that default value is in the range between minimum and maximum ones.
    if (!is_null($def_value) &&
        ($def_value < $min_value || $def_value > $max_value))
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_date] Default value is out of range.');
        return ERROR_DEFAULT_VALUE_OUT_OF_RANGE;
    }

    return NO_ERROR;
}

/**
 * Validates 'Duration' field information before creation or modification.
 *
 * @param string $field_name {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_name Field name}.
 * @param int $min_value Minimum allowed value of the field.
 * @param int $max_value Maximum allowed value of the field.
 * @param int $def_value Default allowed value of the field (NULL by default).
 * @return int Error code:
 * <ul>
 * <li>{@link NO_ERROR} - data are valid</li>
 * <li>{@link ERROR_INCOMPLETE_FORM} - at least one of required field is empty</li>
 * <li>{@link ERROR_INVALID_TIME_VALUE} - at least one of specified duration values is invalid</li>
 * <li>{@link ERROR_TIME_VALUE_OUT_OF_RANGE} - at least one of specified duration values is less than {@link MIN_FIELD_DURATION}, or greater than {@link MAX_FIELD_DURATION}</li>
 * <li>{@link ERROR_MIN_MAX_VALUES} - maximum value is less than minimum one</li>
 * <li>{@link ERROR_DEFAULT_VALUE_OUT_OF_RANGE} - default value is less than $min_value, or greater than $max_value</li>
 * </ul>
 */
function field_validate_duration ($field_name, $min_value, $max_value, $def_value = NULL)
{
    debug_write_log(DEBUG_TRACE, '[field_validate_duration]');
    debug_write_log(DEBUG_DUMP,  '[field_validate_duration] $field_name = ' . $field_name);
    debug_write_log(DEBUG_DUMP,  '[field_validate_duration] $min_value  = ' . $min_value);
    debug_write_log(DEBUG_DUMP,  '[field_validate_duration] $max_value  = ' . $max_value);
    debug_write_log(DEBUG_DUMP,  '[field_validate_duration] $def_value  = ' . $def_value);

    // Check that field name and specified values are not empty.
    if (ustrlen($field_name) == 0 ||
        ustrlen($min_value)  == 0 ||
        ustrlen($max_value)  == 0)
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_duration] At least one required field is empty.');
        return ERROR_INCOMPLETE_FORM;
    }

    // Convert specified minimum and maximum duration values to amount of minutes.
    $min_duration = ustr2time($min_value);
    $max_duration = ustr2time($max_value);
    $def_duration = (is_null($def_value) ? NULL : ustr2time($def_value));

    if ($min_duration == -1 ||
        $max_duration == -1 ||
        $def_duration == -1)
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_duration] Invalid duration value.');
        return ERROR_INVALID_TIME_VALUE;
    }

    // Check that specified values are in the range of valid values.
    if ($min_duration < MIN_FIELD_DURATION || $min_duration > MAX_FIELD_DURATION ||
        $max_duration < MIN_FIELD_DURATION || $max_duration > MAX_FIELD_DURATION)
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_duration] Duration value is out of range.');
        return ERROR_TIME_VALUE_OUT_OF_RANGE;
    }

    if ($min_duration >= $max_duration)
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_duration] Minimum value is greater then maximum one.');
        return ERROR_MIN_MAX_VALUES;
    }

    // Check that default value is in the range between minimum and maximum ones.
    if (!is_null($def_duration) &&
        ($def_duration < $min_duration || $def_duration > $max_duration))
    {
        debug_write_log(DEBUG_NOTICE, '[field_validate_duration] Default value is out of range.');
        return ERROR_DEFAULT_VALUE_OUT_OF_RANGE;
    }

    return NO_ERROR;
}

/**
 * @ignore Going to be obsolete soon due to 'new-555'.
 */
function field_create_list_items ($state_id, $field_name, $list_items)
{
    debug_write_log(DEBUG_TRACE, '[field_create_list_items]');
    debug_write_log(DEBUG_DUMP,  '[field_create_list_items] $state_id   = ' . $state_id);
    debug_write_log(DEBUG_DUMP,  '[field_create_list_items] $field_name = ' . $field_name);
    debug_write_log(DEBUG_DUMP,  '[field_create_list_items] $list_items = ' . $list_items);

    $rs = dal_query('fields/fndk.sql', $state_id, ustrtolower($field_name));

    if ($rs->rows == 0)
    {
        debug_write_log(DEBUG_ERROR, '[field_create_list_items] Field not found.');
        return ERROR_NOT_FOUND;
    }

    $field_id = $rs->fetch('field_id');

    if (DATABASE_DRIVER == DRIVER_MYSQL50)
    {
        $rs = dal_query('values/mysql/lvselall.sql', $field_id);

        while (($row = $rs->fetch()))
        {
            dal_query('values/mysql/lvdelete.sql', $field_id, $row['int_value']);
        }
    }
    else
    {
        dal_query('values/lvdelall.sql', $field_id);
    }

    $items = explode("\n", $list_items);

    foreach ($items as $item)
    {
        $item = trim($item);

        if (ustrlen($item) == 0)
        {
            debug_write_log(DEBUG_NOTICE, '[field_create_list_items] Line is empty.');
            continue;
        }

        $pos = ustrpos($item, ' ');

        if ($pos === FALSE)
        {
            debug_write_log(DEBUG_NOTICE, '[field_create_list_items] Values separator not found.');
            continue;
        }

        $int_value = ustrcut(usubstr($item, 0, $pos), ustrlen(MAXINT));
        $str_value = ustrcut(usubstr($item, $pos + 1), MAX_LISTITEM_NAME);

        if (!is_intvalue($int_value))
        {
            debug_write_log(DEBUG_NOTICE, '[field_create_list_items] Invalid integer value.');
            continue;
        }

        if ($int_value < 1 || $int_value > MAXINT)
        {
            debug_write_log(DEBUG_NOTICE, '[field_create_list_items] Integer value is out of range.');
            continue;
        }

        $rs = dal_query('values/lvfndk2.sql', $field_id, $str_value);

        if ($rs->rows != 0)
        {
            debug_write_log(DEBUG_NOTICE, '[field_create_list_items] Specified list item already exists.');
            continue;
        }

        $rs = dal_query('values/lvfndk1.sql', $field_id, $int_value);

        dal_query(($rs->rows == 0 ? 'values/lvcreate.sql' : 'values/lvmodify.sql'),
                  $field_id,
                  $int_value,
                  $str_value);
    }

    return NO_ERROR;
}

/**
 * @ignore Going to be obsolete soon due to 'new-555'.
 */
function field_pickup_list_items ($field_id)
{
    debug_write_log(DEBUG_TRACE, '[field_pickup_list_items]');
    debug_write_log(DEBUG_DUMP,  '[field_pickup_list_items] $field_id = ' . $field_id);

    $list_items = NULL;

    $rs = dal_query('values/lvlist.sql', $field_id);

    while (($row = $rs->fetch()))
    {
        $list_items .= sprintf("%u %s\n", $row['int_value'], $row['str_value']);
    }

    return $list_items;
}

/**
 * Creates new field.
 *
 * @param int $state_id {@link http://www.etraxis.org/docs-schema.php#tbl_states_state_id ID} of state which new field will belong to.
 * @param string $field_name {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_name Field name}.
 * @param int $field_type {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_type Field type}.
 * @param bool $is_required Whether the field is required.
 * @param bool $add_separator If TRUE, then eTraxis will add separator '<hr>' after the field, when record is being displayed.
 * @param bool $guest_access Ability of {@link http://www.etraxis.org/docs-schema.php#tbl_fields_guest_access guest access} to the field values.
 * @param string $regex_check Perl-compatible regular expression, which values of the field must conform to.
 * @param string $regex_search Perl-compatible regular expression to modify values of the field, used to be searched for  (NULL by default).
 * @param string $regex_replace Perl-compatible regular expression to modify values of the field, used to replace with (NULL by default).
 * @param int $param1 {@link http://www.etraxis.org/docs-schema.php#tbl_fields_param1 First parameter} of the field, specific to its type (NULL by default).
 * @param int $param2 {@link http://www.etraxis.org/docs-schema.php#tbl_fields_param2 Second parameter} of the field, specific to its type (NULL by default).
 * @param int $value_id {@link http://www.etraxis.org/docs-schema.php#tbl_fields_value_id Default value} of the field, specific to its type (NULL by default).
 * @return int Error code:
 * <ul>
 * <li>{@link NO_ERROR} - field is successfully created</li>
 * <li>{@link ERROR_INCOMPLETE_FORM} - at least one of required data is empty</li>
 * <li>{@link ERROR_ALREADY_EXISTS} - field with specified {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_name name} already exists</li>
 * <li>{@link ERROR_NOT_FOUND} - failure on attempt to create field</li>
 * </ul>
 */
function field_create ($state_id, $field_name, $field_type, $is_required, $add_separator, $guest_access,
                       $regex_check = NULL, $regex_search = NULL, $regex_replace = NULL,
                       $param1 = NULL, $param2 = NULL, $value_id = NULL)
{
    debug_write_log(DEBUG_TRACE, '[field_create]');
    debug_write_log(DEBUG_DUMP,  '[field_create] $state_id      = ' . $state_id);
    debug_write_log(DEBUG_DUMP,  '[field_create] $field_name    = ' . $field_name);
    debug_write_log(DEBUG_DUMP,  '[field_create] $field_type    = ' . $field_type);
    debug_write_log(DEBUG_DUMP,  '[field_create] $is_required   = ' . $is_required);
    debug_write_log(DEBUG_DUMP,  '[field_create] $add_separator = ' . $add_separator);
    debug_write_log(DEBUG_DUMP,  '[field_create] $guest_access  = ' . $guest_access);
    debug_write_log(DEBUG_DUMP,  '[field_create] $regex_check   = ' . $regex_check);
    debug_write_log(DEBUG_DUMP,  '[field_create] $regex_search  = ' . $regex_search);
    debug_write_log(DEBUG_DUMP,  '[field_create] $regex_replace = ' . $regex_replace);
    debug_write_log(DEBUG_DUMP,  '[field_create] $param1        = ' . $param1);
    debug_write_log(DEBUG_DUMP,  '[field_create] $param2        = ' . $param2);
    debug_write_log(DEBUG_DUMP,  '[field_create] $value_id      = ' . $value_id);

    // Check that field name is not empty.
    if (ustrlen($field_name) == 0)
    {
        debug_write_log(DEBUG_NOTICE, '[field_create] At least one required field is empty.');
        return ERROR_INCOMPLETE_FORM;
    }

    // Check that there is no field with the same name in the specified state.
    $rs = dal_query('fields/fndk.sql', $state_id, ustrtolower($field_name));

    if ($rs->rows != 0)
    {
        debug_write_log(DEBUG_NOTICE, '[field_create] Field already exists.');
        return ERROR_ALREADY_EXISTS;
    }

    // Calculates field order.
    $rs = dal_query('fields/count.sql', $state_id);
    $field_order = $rs->fetch(0) + 1;

    // Create a field.
    dal_query('fields/create.sql',
              $state_id,
              $field_name,
              $field_order,
              $field_type,
              bool2sql($is_required),
              bool2sql($add_separator),
              bool2sql($guest_access),
              ustrlen($regex_check)   == 0 ? NULL : $regex_check,
              ustrlen($regex_search)  == 0 ? NULL : $regex_search,
              ustrlen($regex_replace) == 0 ? NULL : $regex_replace,
              is_null($param1)             ? NULL : $param1,
              is_null($param2)             ? NULL : $param2,
              is_null($value_id)           ? NULL : $value_id);

    $rs = dal_query('fields/fndk.sql', $state_id, ustrtolower($field_name));

    if ($rs->rows == 0)
    {
        debug_write_log(DEBUG_NOTICE, '[field_create] Field cannot be found.');
        return ERROR_NOT_FOUND;
    }

    $row = $rs->fetch();

    // Give permission to read to all local project groups.
    dal_query('fields/fpaddall.sql',
              $row['project_id'],
              $row['field_id'],
              FIELD_ALLOW_TO_READ);

    // Give permission to write to all local project groups.
    dal_query('fields/fpaddall.sql',
              $row['project_id'],
              $row['field_id'],
              FIELD_ALLOW_TO_WRITE);

    return NO_ERROR;
}

/**
 * Modifies specified field.
 *
 * @param int $id {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_id ID} of field to be modified.
 * @param int $state_id {@link http://www.etraxis.org/docs-schema.php#tbl_states_state_id ID} of state which the field belongs to.
 * @param string $field_name New {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_name field name}.
 * @param int $field_old_order Current {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_order field order}.
 * @param int $field_new_order New {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_order field order}.
 * @param int $field_type Current {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_type field type} (field type is not modifiable).
 * @param bool $is_required Whether the field is required.
 * @param bool $add_separator If TRUE, then eTraxis will add separator '<hr>' after the field, when record is being displayed.
 * @param bool $guest_access Ability of {@link http://www.etraxis.org/docs-schema.php#tbl_fields_guest_access guest access} to the field values.
 * @param string $regex_check New perl-compatible regular expression, which values of the field must conform to.
 * @param string $regex_search New perl-compatible regular expression to modify values of the field, used to be searched for  (NULL by default).
 * @param string $regex_replace New perl-compatible regular expression to modify values of the field, used to replace with (NULL by default).
 * @param int $param1 New {@link http://www.etraxis.org/docs-schema.php#tbl_fields_param1 first parameter} of the field, specific to its type (NULL by default).
 * @param int $param2 New {@link http://www.etraxis.org/docs-schema.php#tbl_fields_param2 second parameter} of the field, specific to its type (NULL by default).
 * @param int $value_id New {@link http://www.etraxis.org/docs-schema.php#tbl_fields_value_id default value} of the field, specific to its type (NULL by default).
 * @return int Error code:
 * <ul>
 * <li>{@link NO_ERROR} - field is successfully modified</li>
 * <li>{@link ERROR_INCOMPLETE_FORM} - at least one of required data is empty</li>
 * <li>{@link ERROR_ALREADY_EXISTS} - another field with specified {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_name name} already exists</li>
 * </ul>
 */
function field_modify ($id, $state_id, $field_name, $field_old_order, $field_new_order, $field_type, $is_required, $add_separator, $guest_access,
                       $regex_check = NULL, $regex_search = NULL, $regex_replace = NULL,
                       $param1 = NULL, $param2 = NULL, $value_id = NULL)
{
    debug_write_log(DEBUG_TRACE, '[field_modify]');
    debug_write_log(DEBUG_DUMP,  '[field_modify] $id              = ' . $id);
    debug_write_log(DEBUG_DUMP,  '[field_modify] $state_id        = ' . $state_id);
    debug_write_log(DEBUG_DUMP,  '[field_modify] $field_name      = ' . $field_name);
    debug_write_log(DEBUG_DUMP,  '[field_modify] $field_old_order = ' . $field_old_order);
    debug_write_log(DEBUG_DUMP,  '[field_modify] $field_new_order = ' . $field_new_order);
    debug_write_log(DEBUG_DUMP,  '[field_modify] $field_type      = ' . $field_type);
    debug_write_log(DEBUG_DUMP,  '[field_modify] $is_required     = ' . $is_required);
    debug_write_log(DEBUG_DUMP,  '[field_modify] $add_separator   = ' . $add_separator);
    debug_write_log(DEBUG_DUMP,  '[field_modify] $guest_access    = ' . $guest_access);
    debug_write_log(DEBUG_DUMP,  '[field_modify] $regex_check     = ' . $regex_check);
    debug_write_log(DEBUG_DUMP,  '[field_modify] $regex_search    = ' . $regex_search);
    debug_write_log(DEBUG_DUMP,  '[field_modify] $regex_replace   = ' . $regex_replace);
    debug_write_log(DEBUG_DUMP,  '[field_modify] $param1          = ' . $param1);
    debug_write_log(DEBUG_DUMP,  '[field_modify] $param2          = ' . $param2);
    debug_write_log(DEBUG_DUMP,  '[field_modify] $value_id        = ' . $value_id);

    // Check that field name is not empty.
    if (ustrlen($field_name) == 0)
    {
        debug_write_log(DEBUG_NOTICE, '[field_modify] At least one required field is empty.');
        return ERROR_INCOMPLETE_FORM;
    }

    // Check that there is no field with the same name, besides this one.
    $rs = dal_query('fields/fndku.sql', $id, $state_id, ustrtolower($field_name));

    if ($rs->rows != 0)
    {
        debug_write_log(DEBUG_NOTICE, '[field_modify] Field already exists.');
        return ERROR_ALREADY_EXISTS;
    }

    // Reorder field.
    $rs = dal_query('fields/count.sql', $state_id);

    if ($field_new_order < 1 || $field_new_order > $rs->fetch(0))
    {
        debug_write_log(DEBUG_NOTICE, '[field_modify] Field order is out of range.');
    }
    elseif ($field_old_order < $field_new_order)
    {
        debug_write_log(DEBUG_NOTICE, '[field_modify] Field order is being changed (going down).');

        dal_query('fields/setorder.sql', $state_id, $field_old_order, 0);

        for ($i = $field_old_order; $i < $field_new_order; $i++)
        {
            dal_query('fields/setorder.sql', $state_id, $i + 1, $i);
        }

        dal_query('fields/setorder.sql', $state_id, 0, $field_new_order);
    }
    elseif ($field_old_order > $field_new_order)
    {
        debug_write_log(DEBUG_NOTICE, '[field_modify] Field order is being changed (going up).');

        dal_query('fields/setorder.sql', $state_id, $field_old_order, 0);

        for ($i = $field_old_order; $i > $field_new_order; $i--)
        {
            dal_query('fields/setorder.sql', $state_id, $i - 1, $i);
        }

        dal_query('fields/setorder.sql', $state_id, 0, $field_new_order);
    }

    // Modify the field.
    dal_query('fields/modify.sql',
              $id,
              $field_name,
              bool2sql($is_required),
              bool2sql($add_separator),
              bool2sql($guest_access),
              ustrlen($regex_check)   == 0 ? NULL : $regex_check,
              ustrlen($regex_search)  == 0 ? NULL : $regex_search,
              ustrlen($regex_replace) == 0 ? NULL : $regex_replace,
              is_null($param1)             ? NULL : $param1,
              is_null($param2)             ? NULL : $param2,
              is_null($value_id)           ? NULL : $value_id);

    return NO_ERROR;
}

/**
 * Checks whether field can be deleted.
 *
 * @param int $id {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_id ID} of field to be deleted.
 * @return bool TRUE if field can be deleted, FALSE otherwise.
 */
function is_field_removable ($id)
{
    debug_write_log(DEBUG_TRACE, '[is_field_removable]');
    debug_write_log(DEBUG_DUMP,  '[is_field_removable] $id = ' . $id);

    $rs = dal_query('fields/fvfndc.sql', $id);

    return ($rs->fetch(0) == 0);
}

/**
 * Deletes specified field if it's removable, or disables the field otherwise.
 *
 * @param int $id {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_id ID} of field to be deleted.
 * @return int Error code:
 * <ul>
 * <li>{@link NO_ERROR} - field is successfully deleted/disabled</li>
 * <li>{@link ERROR_NOT_FOUND} - specified field cannot be found</li>
 * </ul>
 */
function field_delete ($id)
{
    debug_write_log(DEBUG_TRACE, '[field_delete]');
    debug_write_log(DEBUG_DUMP,  '[field_delete] $id = ' . $id);

    // Find field in database.
    $field = field_find($id);

    if (!$field)
    {
        debug_write_log(DEBUG_NOTICE, '[field_delete] Field cannot be found.');
        return ERROR_NOT_FOUND;
    }

    // Get current number of fields.
    $rs = dal_query('fields/count.sql', $field['state_id']);
    $last_field = $rs->fetch(0);

    if (is_field_removable($id))
    {
        // Delete all related data.
        dal_query('fields/lvdelall.sql', $id);
        dal_query('fields/ffdelall.sql', $id);
        dal_query('fields/fpdelall.sql', $id);
        dal_query('fields/delete.sql',   $id);
    }
    else
    {
        // Disable the field.
        dal_query('fields/disable.sql', $id, time());
    }

    // Reorder rest of fields in the same state.
    for ($i = $field['field_order']; $i < $last_field; $i++)
    {
        dal_query('fields/setorder.sql', $field['state_id'], $i + 1, $i);
    }

    return NO_ERROR;
}

/**
 * Sets permissions of system role 'author' for specified field.
 *
 * @param int $fid {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_id ID} of field which permissions should be set for.
 * @param int $perm New {@link http://www.etraxis.org/docs-schema.php#tbl_fields_author_perm permissions} set.
 * @return int Always {@link NO_ERROR}.
 */
function field_author_permission_set ($fid, $perm)
{
    debug_write_log(DEBUG_TRACE, '[field_author_permission_set]');
    debug_write_log(DEBUG_DUMP,  '[field_author_permission_set] $fid  = ' . $fid);
    debug_write_log(DEBUG_DUMP,  '[field_author_permission_set] $perm = ' . $perm);

    dal_query('fields/apset.sql', $fid, $perm);

    return NO_ERROR;
}

/**
 * Sets permissions of system role 'responsible' for specified field.
 *
 * @param int $fid {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_id ID} of field which permissions should be set for.
 * @param int $perm New {@link http://www.etraxis.org/docs-schema.php#tbl_fields_responsible_perm permissions} set.
 * @return int Always {@link NO_ERROR}.
 */
function field_responsible_permission_set ($fid, $perm)
{
    debug_write_log(DEBUG_TRACE, '[field_responsible_permission_set]');
    debug_write_log(DEBUG_DUMP,  '[field_responsible_permission_set] $fid  = ' . $fid);
    debug_write_log(DEBUG_DUMP,  '[field_responsible_permission_set] $perm = ' . $perm);

    dal_query('fields/rpset.sql', $fid, $perm);

    return NO_ERROR;
}

/**
 * Sets permissions of system role 'registered' for specified field.
 *
 * @param int $fid {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_id ID} of field which permissions should be set for.
 * @param int $perm New {@link http://www.etraxis.org/docs-schema.php#tbl_fields_registered_perm permissions} set.
 * @return int Always {@link NO_ERROR}.
 */
function field_registered_permission_set ($fid, $perm)
{
    debug_write_log(DEBUG_TRACE, '[field_registered_permission_set]');
    debug_write_log(DEBUG_DUMP,  '[field_registered_permission_set] $fid  = ' . $fid);
    debug_write_log(DEBUG_DUMP,  '[field_registered_permission_set] $perm = ' . $perm);

    dal_query('fields/r2pset.sql', $fid, $perm);

    return NO_ERROR;
}

/**
 * Gives to specified group a specified permission for specified field.
 *
 * @param int $fid {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_id Field ID}.
 * @param int $gid {@link http://www.etraxis.org/docs-schema.php#tbl_groups_group_id Group ID}.
 * @param int $perm Permission (exactly one of the following):
 * <ul>
 * <li>{@link FIELD_ALLOW_TO_READ}</li>
 * <li>{@link FIELD_ALLOW_TO_WRITE}</li>
 * </ul>
 * @return int Always {@link NO_ERROR}.
 */
function field_permission_add ($fid, $gid, $perm)
{
    debug_write_log(DEBUG_TRACE, '[field_permission_add]');
    debug_write_log(DEBUG_DUMP,  '[field_permission_add] $fid  = ' . $fid);
    debug_write_log(DEBUG_DUMP,  '[field_permission_add] $gid  = ' . $gid);
    debug_write_log(DEBUG_DUMP,  '[field_permission_add] $perm = ' . $perm);

    dal_query('fields/fpadd.sql', $fid, $gid, $perm);

    return NO_ERROR;
}

/**
 * Revokes from specified group all permissions for specified field.
 *
 * @param int $fid {@link http://www.etraxis.org/docs-schema.php#tbl_fields_field_id Field ID}.
 * @param int $gid {@link http://www.etraxis.org/docs-schema.php#tbl_groups_group_id Group ID}.
 * @return int Always {@link NO_ERROR}.
 */
function field_permission_remove ($fid, $gid)
{
    debug_write_log(DEBUG_TRACE, '[field_permission_remove]');
    debug_write_log(DEBUG_DUMP,  '[field_permission_remove] $fid  = ' . $fid);
    debug_write_log(DEBUG_DUMP,  '[field_permission_remove] $gid  = ' . $gid);

    dal_query('fields/fpremove.sql', $fid, $gid);

    return NO_ERROR;
}

/**
 * Exports all fields of the specified state to XML code (see also {@link template_export}).
 *
 * @param int $id {@link http://www.etraxis.org/docs-schema.php#tbl_states_state_id ID} of state, which fields should be exported.
 * @param array &$groups Array of IDs of groups, affected by this template (used for output only).
 * @return string Generated XML code.
 */
function field_export ($id, &$groups)
{
    debug_write_log(DEBUG_TRACE, '[field_export]');
    debug_write_log(DEBUG_DUMP,  '[field_export] $id = ' . $id);

    // Allocation of field types to XML code.
    $types = array
    (
        FIELD_TYPE_NUMBER     => 'number',
        FIELD_TYPE_STRING     => 'string',
        FIELD_TYPE_MULTILINED => 'multi',
        FIELD_TYPE_CHECKBOX   => 'check',
        FIELD_TYPE_LIST       => 'list',
        FIELD_TYPE_RECORD     => 'record',
        FIELD_TYPE_DATE       => 'date',
        FIELD_TYPE_DURATION   => 'duration',
    );

    // List all fields of the state.
    $rs = dal_query('fields/list.sql', $id, 'field_order');

    if ($rs->rows == 0)
    {
        return NULL;
    }

    $xml = "        <fields>\n";

    // Add XML code for each found field.
    while (($field = $rs->fetch()))
    {
        // Add XML code for general field information.
        $xml .= sprintf("          <field name=\"%s\" type=\"%s\" required=\"%s\" separator=\"%s\"",
                        ustr2html($field['field_name']),
                        $types[$field['field_type']],
                        ($field['is_required'] ? 'yes' : 'no'),
                        ($field['add_separator'] ? 'add' : 'none'));

        // Add XML code for information, specific to type of the field.
        switch ($field['field_type'])
        {
            case FIELD_TYPE_NUMBER:
                $xml .= sprintf(' minimum="%u" maximum="%u"', $field['param1'], $field['param2']);
                break;

            case FIELD_TYPE_STRING:
            case FIELD_TYPE_MULTILINED:
                $xml .= sprintf(' length="%u"', $field['param1']);
                break;

            case FIELD_TYPE_DATE:
                $xml .= sprintf(' minimum="%s" maximum="%s"', $field['param1'], $field['param2']);
                break;

            case FIELD_TYPE_DURATION:
                $xml .= sprintf(' minimum="%s" maximum="%s"', time2ustr($field['param1']), time2ustr($field['param2']));
                break;

            default: ;  // nop
        }

        // If default value is specified, add XML code for it.
        if (!is_null($field['value_id']))
        {
            switch ($field['field_type'])
            {
                case FIELD_TYPE_NUMBER:
                case FIELD_TYPE_LIST:
                    $xml .= sprintf(' default="%u"', $field['value_id']);
                    break;

                case FIELD_TYPE_STRING:
                    $xml .= sprintf(' default="%s"', ustr2html(value_find(FIELD_TYPE_STRING, $field['value_id'])));
                    break;

                case FIELD_TYPE_CHECKBOX:
                    $xml .= sprintf(' default="%u"', ($field['value_id'] ? 'on' : 'off'));
                    break;

                case FIELD_TYPE_DATE:
                    $xml .= sprintf(' default="%s"', $field['value_id']);
                    break;

                case FIELD_TYPE_DURATION:
                    $xml .= sprintf(' default="%s"', time2ustr($field['value_id']));
                    break;

                default: ;  // nop
            }
        }

        // If RegEx values are specified, add XML code for them.
        if ($field['field_type'] == FIELD_TYPE_STRING ||
            $field['field_type'] == FIELD_TYPE_MULTILINED)
        {
            if (!is_null($field['regex_check']))
            {
                $xml .= sprintf(' regex_check="%s"', ustr2html($field['regex_check']));
            }

            if (!is_null($field['regex_search']))
            {
                $xml .= sprintf(' regex_search="%s"', ustr2html($field['regex_search']));
            }

            if (!is_null($field['regex_replace']))
            {
                $xml .= sprintf(' regex_replace="%s"', ustr2html($field['regex_replace']));
            }
        }

        $xml .= ">\n";

        // Default value of 'multilined' field is processed in a specific way (must be out in dedicated XML tags).
        if ($field['field_type'] == FIELD_TYPE_MULTILINED)
        {
            $default = value_find(FIELD_TYPE_MULTILINED, $field['value_id']);

            if (!is_null($default))
            {
                $xml .= "            <default>\n";
                $xml .= ustr2html($default) . "\n";
                $xml .= "            </default>\n";
            }
        }

        // If field type is 'list', enumerate all its items.
        if ($field['field_type'] == FIELD_TYPE_LIST)
        {
            $rsl = dal_query('values/lvlist.sql', $field['field_id']);

            if ($rsl->rows != 0)
            {
                $xml .= "            <list>\n";

                while (($item = $rsl->fetch()))
                {
                    $xml .= "              <item value=\"{$item['int_value']}\">" . ustr2html($item['str_value']) . "</item>\n";
                }

                $xml .= "            </list>\n";
            }
        }

        // Enumerate permissions of all groups for this field.
        $rsp = dal_query('fields/fplist.sql', $field['field_id']);

        if ($rsp->rows != 0)
        {
            $xml .= "            <permissions>\n";

            while (($group = $rsp->fetch()))
            {
                array_push($groups, $group['group_id']);

                $xml .= sprintf("              <group name=\"%s\" type=\"%s\">",
                                ustr2html($group['group_name']),
                                (is_null($group['project_id']) ? 'global' : 'local'));

                $xml .= ($group['perms'] == FIELD_ALLOW_TO_WRITE ? 'write' : 'read');
                $xml .= "</group>\n";
            }

            $xml .= "            </permissions>\n";
        }

        $xml .= "          </field>\n";
    }

    $xml .= "        </fields>\n";

    return $xml;
}

?>
Return current item: ETraxis