[Evolvis-commits] r9: Evolvis GForge Files added

sfromm at evolvis.org sfromm at evolvis.org
Wed Feb 13 14:04:51 CET 2008


Author: sfromm
Date: 2008-02-13 13:04:48 +0000 (Wed, 13 Feb 2008)
New Revision: 9

Added:
   trunk/gforge_base/gforge/backend/
   trunk/gforge_base/gforge/backend/DatabaseDump.pl
   trunk/gforge_base/gforge/backend/include.pl
   trunk/gforge_base/gforge/backend/shell/
   trunk/gforge_base/gforge/backend/shell/apache.sh
   trunk/gforge_base/gforge/backend/zones/
   trunk/gforge_base/gforge/backend/zones/aliases.zone
   trunk/gforge_base/gforge/backend/zones/dns.zone
   trunk/gforge_base/gforge/backend/zones/sendmail.cw.zone
   trunk/gforge_base/gforge/common/
   trunk/gforge_base/gforge/common/docman/
   trunk/gforge_base/gforge/common/docman/Document.class
   trunk/gforge_base/gforge/common/docman/DocumentFactory.class
   trunk/gforge_base/gforge/common/docman/DocumentGroup.class
   trunk/gforge_base/gforge/common/docman/DocumentGroupFactory.class
   trunk/gforge_base/gforge/common/forum/
   trunk/gforge_base/gforge/common/forum/Forum.class
   trunk/gforge_base/gforge/common/forum/ForumFactory.class
   trunk/gforge_base/gforge/common/forum/ForumMessage.class
   trunk/gforge_base/gforge/common/forum/ForumMessageFactory.class
   trunk/gforge_base/gforge/common/forum/ForumsForUser.class
   trunk/gforge_base/gforge/common/frs/
   trunk/gforge_base/gforge/common/frs/FRSFile.class
   trunk/gforge_base/gforge/common/frs/FRSPackage.class
   trunk/gforge_base/gforge/common/frs/FRSRelease.class
   trunk/gforge_base/gforge/common/include/
   trunk/gforge_base/gforge/common/include/Error.class
   trunk/gforge_base/gforge/common/include/GForge.class
   trunk/gforge_base/gforge/common/include/Group.class
   trunk/gforge_base/gforge/common/include/GroupJoinRequest.class
   trunk/gforge_base/gforge/common/include/Jabber.class
   trunk/gforge_base/gforge/common/include/MailParser.class
   trunk/gforge_base/gforge/common/include/Permission.class
   trunk/gforge_base/gforge/common/include/Plugin.class
   trunk/gforge_base/gforge/common/include/PluginManager.class
   trunk/gforge_base/gforge/common/include/Role.class
   trunk/gforge_base/gforge/common/include/RoleObserver.class
   trunk/gforge_base/gforge/common/include/SCM.class
   trunk/gforge_base/gforge/common/include/Stats.class
   trunk/gforge_base/gforge/common/include/System.class
   trunk/gforge_base/gforge/common/include/User.class
   trunk/gforge_base/gforge/common/include/Validator.class
   trunk/gforge_base/gforge/common/include/account.php
   trunk/gforge_base/gforge/common/include/constants.php
   trunk/gforge_base/gforge/common/include/cron_utils.php
   trunk/gforge_base/gforge/common/include/database.php
   trunk/gforge_base/gforge/common/include/escapingUtils.php
   trunk/gforge_base/gforge/common/include/license.php
   trunk/gforge_base/gforge/common/include/scm.php
   trunk/gforge_base/gforge/common/include/session.php
   trunk/gforge_base/gforge/common/include/system/
   trunk/gforge_base/gforge/common/include/system/LDAP.class
   trunk/gforge_base/gforge/common/include/system/NSSPGSQL.class
   trunk/gforge_base/gforge/common/include/system/UNIX.class
   trunk/gforge_base/gforge/common/include/system/pgsql.class
   trunk/gforge_base/gforge/common/include/timezones.php
   trunk/gforge_base/gforge/common/include/utils.php
   trunk/gforge_base/gforge/common/mail/
   trunk/gforge_base/gforge/common/mail/MailingList.class
   trunk/gforge_base/gforge/common/mail/MailingListFactory.class
   trunk/gforge_base/gforge/common/pm/
   trunk/gforge_base/gforge/common/pm/ProjectCategory.class
   trunk/gforge_base/gforge/common/pm/ProjectGroup.class
   trunk/gforge_base/gforge/common/pm/ProjectGroupFactory.class
   trunk/gforge_base/gforge/common/pm/ProjectTask.class
   trunk/gforge_base/gforge/common/pm/ProjectTaskFactory.class
   trunk/gforge_base/gforge/common/pm/ProjectTasksForUser.class
   trunk/gforge_base/gforge/common/pm/Validator.class
   trunk/gforge_base/gforge/common/pm/import_utils.php
   trunk/gforge_base/gforge/common/reporting/
   trunk/gforge_base/gforge/common/reporting/Report.class
   trunk/gforge_base/gforge/common/reporting/ReportGroupAdded.class
   trunk/gforge_base/gforge/common/reporting/ReportGroupCum.class
   trunk/gforge_base/gforge/common/reporting/ReportProjectAct.class
   trunk/gforge_base/gforge/common/reporting/ReportProjectTime.class
   trunk/gforge_base/gforge/common/reporting/ReportSetup.class
   trunk/gforge_base/gforge/common/reporting/ReportSiteAct.class
   trunk/gforge_base/gforge/common/reporting/ReportSiteTime.class
   trunk/gforge_base/gforge/common/reporting/ReportTrackerAct.class
   trunk/gforge_base/gforge/common/reporting/ReportUserAct.class
   trunk/gforge_base/gforge/common/reporting/ReportUserAdded.class
   trunk/gforge_base/gforge/common/reporting/ReportUserCum.class
   trunk/gforge_base/gforge/common/reporting/ReportUserTime.class
   trunk/gforge_base/gforge/common/reporting/TimeEntry.class
   trunk/gforge_base/gforge/common/reporting/report_utils.php
   trunk/gforge_base/gforge/common/scm/
   trunk/gforge_base/gforge/common/scm/SCMFactory.class
   trunk/gforge_base/gforge/common/search/
   trunk/gforge_base/gforge/common/search/ArtifactSearchQuery.class
   trunk/gforge_base/gforge/common/search/DocsSearchQuery.class
   trunk/gforge_base/gforge/common/search/ExportProjectSearchQuery.class
   trunk/gforge_base/gforge/common/search/ForumSearchQuery.class
   trunk/gforge_base/gforge/common/search/ForumsSearchQuery.class
   trunk/gforge_base/gforge/common/search/FrsSearchQuery.class
   trunk/gforge_base/gforge/common/search/NewsSearchQuery.class
   trunk/gforge_base/gforge/common/search/PeopleSearchQuery.class
   trunk/gforge_base/gforge/common/search/ProjectSearchQuery.class
   trunk/gforge_base/gforge/common/search/SearchQuery.class
   trunk/gforge_base/gforge/common/search/SkillSearchQuery.class
   trunk/gforge_base/gforge/common/search/TasksSearchQuery.class
   trunk/gforge_base/gforge/common/search/TrackersSearchQuery.class
   trunk/gforge_base/gforge/common/tracker/
   trunk/gforge_base/gforge/common/tracker/Artifact.class
   trunk/gforge_base/gforge/common/tracker/ArtifactBoxOptions.class
   trunk/gforge_base/gforge/common/tracker/ArtifactCanned.class
   trunk/gforge_base/gforge/common/tracker/ArtifactExtraField.class
   trunk/gforge_base/gforge/common/tracker/ArtifactExtraFieldElement.class
   trunk/gforge_base/gforge/common/tracker/ArtifactFactory.class
   trunk/gforge_base/gforge/common/tracker/ArtifactFile.class
   trunk/gforge_base/gforge/common/tracker/ArtifactFromID.class
   trunk/gforge_base/gforge/common/tracker/ArtifactHistory.class
   trunk/gforge_base/gforge/common/tracker/ArtifactMessage.class
   trunk/gforge_base/gforge/common/tracker/ArtifactQuery.class
   trunk/gforge_base/gforge/common/tracker/ArtifactQueryFactory.class
   trunk/gforge_base/gforge/common/tracker/ArtifactType.class
   trunk/gforge_base/gforge/common/tracker/ArtifactTypeFactory.class
   trunk/gforge_base/gforge/common/tracker/ArtifactTypes.class
   trunk/gforge_base/gforge/common/tracker/Artifacts.class
   trunk/gforge_base/gforge/common/tracker/ArtifactsForUser.class
   trunk/gforge_base/gforge/common/tracker/artifact_type_definitions.php
   trunk/gforge_base/gforge/contrib/
   trunk/gforge_base/gforge/contrib/autoconf/
   trunk/gforge_base/gforge/contrib/autoconf/README.configure
   trunk/gforge_base/gforge/contrib/autoconf/configure
   trunk/gforge_base/gforge/contrib/autoconf/configure.ac
   trunk/gforge_base/gforge/contrib/autoconf/local.inc.in
   trunk/gforge_base/gforge/contrib/autoconf/sample-apache.vhost.in
   trunk/gforge_base/gforge/contrib/beta1_install_from_scratch.txt
   trunk/gforge_base/gforge/contrib/beta1_install_from_scratch_install.php
   trunk/gforge_base/gforge/contrib/gforge-3.0-cronjobs.patch
   trunk/gforge_base/gforge/contrib/gforge-3.0-init_sql.patch
   trunk/gforge_base/gforge/contrib/gforge-3.0-local_config.patch
   trunk/gforge_base/gforge/contrib/gforge.conf
   trunk/gforge_base/gforge/contrib/rh8_apache20_config/
   trunk/gforge_base/gforge/contrib/rh8_apache20_config/httpd.conf
   trunk/gforge_base/gforge/contrib/rh8_apache20_config/local.inc
   trunk/gforge_base/gforge/contrib/rh8_apache20_config/php.conf
   trunk/gforge_base/gforge/contrib/rh8_apache20_config/php.ini
   trunk/gforge_base/gforge/contrib/rh8_apache20_config/readme.txt
   trunk/gforge_base/gforge/contrib/tracker-cc.README
   trunk/gforge_base/gforge/contrib/tracker-cc.patch
   trunk/gforge_base/gforge/contrib/userlist.README
   trunk/gforge_base/gforge/contrib/userlist.patch
   trunk/gforge_base/gforge/cronjobs/
   trunk/gforge_base/gforge/cronjobs/README.root
   trunk/gforge_base/gforge/cronjobs/check_stale_tracker_items.php
   trunk/gforge_base/gforge/cronjobs/crontab.in
   trunk/gforge_base/gforge/cronjobs/cvs-cron/
   trunk/gforge_base/gforge/cronjobs/cvs-cron/cvs.php
   trunk/gforge_base/gforge/cronjobs/cvs-cron/cvscreate.sh
   trunk/gforge_base/gforge/cronjobs/cvs-cron/cvssh.pl
   trunk/gforge_base/gforge/cronjobs/cvs-cron/default_page.php
   trunk/gforge_base/gforge/cronjobs/cvs-cron/ftp_create.php
   trunk/gforge_base/gforge/cronjobs/cvs-cron/grap.c
   trunk/gforge_base/gforge/cronjobs/cvs-cron/history_parse.php
   trunk/gforge_base/gforge/cronjobs/cvs-cron/ssh_create.php
   trunk/gforge_base/gforge/cronjobs/cvs-cron/syncmail
   trunk/gforge_base/gforge/cronjobs/cvs-cron/usergroup.php
   trunk/gforge_base/gforge/cronjobs/cvs-cron/www/
   trunk/gforge_base/gforge/cronjobs/cvs-cron/www/index.php
   trunk/gforge_base/gforge/cronjobs/daily_task_email.php
   trunk/gforge_base/gforge/cronjobs/dav-svn/
   trunk/gforge_base/gforge/cronjobs/dav-svn/README
   trunk/gforge_base/gforge/cronjobs/dav-svn/commit-email_pl
   trunk/gforge_base/gforge/cronjobs/dav-svn/create_docman.php
   trunk/gforge_base/gforge/cronjobs/dav-svn/create_group_home.php
   trunk/gforge_base/gforge/cronjobs/dav-svn/create_groups.php
   trunk/gforge_base/gforge/cronjobs/dav-svn/create_svn.php
   trunk/gforge_base/gforge/cronjobs/dav-svn/create_users.php
   trunk/gforge_base/gforge/cronjobs/dav-svn/crontab.in
   trunk/gforge_base/gforge/cronjobs/dav-svn/svn-index.php
   trunk/gforge_base/gforge/cronjobs/dav-svn/www/
   trunk/gforge_base/gforge/cronjobs/dav-svn/www/index.php
   trunk/gforge_base/gforge/cronjobs/dav-svn/www/svnindex.css
   trunk/gforge_base/gforge/cronjobs/dav-svn/www/svnindex.xsl
   trunk/gforge_base/gforge/cronjobs/db_project_sums.php
   trunk/gforge_base/gforge/cronjobs/db_stats_agg.php
   trunk/gforge_base/gforge/cronjobs/db_trove_maint.php
   trunk/gforge_base/gforge/cronjobs/forum_gateway.php
   trunk/gforge_base/gforge/cronjobs/mail/
   trunk/gforge_base/gforge/cronjobs/mail/mailaliases.php
   trunk/gforge_base/gforge/cronjobs/mail/mailing_lists_create.php
   trunk/gforge_base/gforge/cronjobs/mail/privatize_list.py
   trunk/gforge_base/gforge/cronjobs/massmail.php
   trunk/gforge_base/gforge/cronjobs/project_cleanup.php
   trunk/gforge_base/gforge/cronjobs/project_weekly_metric-backfill.php
   trunk/gforge_base/gforge/cronjobs/project_weekly_metric.php
   trunk/gforge_base/gforge/cronjobs/reporting_cron.php
   trunk/gforge_base/gforge/cronjobs/rotate_activity.php
   trunk/gforge_base/gforge/cronjobs/send_pending_items_mail.php
   trunk/gforge_base/gforge/cronjobs/site_stats.php
   trunk/gforge_base/gforge/cronjobs/stats_projects-backfill.php
   trunk/gforge_base/gforge/cronjobs/stats_projects.inc
   trunk/gforge_base/gforge/cronjobs/stats_site.inc
   trunk/gforge_base/gforge/cronjobs/tracker_gateway.php
   trunk/gforge_base/gforge/cronjobs/update_filesize.php
   trunk/gforge_base/gforge/cronjobs/vacuum.php
   trunk/gforge_base/gforge/db/
   trunk/gforge_base/gforge/db/20001209.sql
   trunk/gforge_base/gforge/db/20001214.sql
   trunk/gforge_base/gforge/db/20001219-sourceforge-2.5
   trunk/gforge_base/gforge/db/20001220.sql
   trunk/gforge_base/gforge/db/20010109.sql
   trunk/gforge_base/gforge/db/20010112.sql
   trunk/gforge_base/gforge/db/20010126.sql
   trunk/gforge_base/gforge/db/20010206.sql
   trunk/gforge_base/gforge/db/20010301.sql
   trunk/gforge_base/gforge/db/20010304-1.sql
   trunk/gforge_base/gforge/db/20010304-2.sql
   trunk/gforge_base/gforge/db/20010304-3.sql
   trunk/gforge_base/gforge/db/20010304-4-artifact-convert-files.php
   trunk/gforge_base/gforge/db/20010305.sql
   trunk/gforge_base/gforge/db/20010313.sql
   trunk/gforge_base/gforge/db/20010317.sql
   trunk/gforge_base/gforge/db/20010409.sql
   trunk/gforge_base/gforge/db/20010412.sql
   trunk/gforge_base/gforge/db/20010507.sql
   trunk/gforge_base/gforge/db/20010509.sql
   trunk/gforge_base/gforge/db/20010511.sql
   trunk/gforge_base/gforge/db/20010601-sourceforge-2.6
   trunk/gforge_base/gforge/db/20021124-1_drop_foundry.sql
   trunk/gforge_base/gforge/db/20021124-2_theming.sql
   trunk/gforge_base/gforge/db/20021124-3_gforge-debian-sf-sync.sql
   trunk/gforge_base/gforge/db/20021125.sql
   trunk/gforge_base/gforge/db/20021212.sql
   trunk/gforge_base/gforge/db/20021213.sql
   trunk/gforge_base/gforge/db/20021213_doc_data-migrate.php
   trunk/gforge_base/gforge/db/20021214.sql
   trunk/gforge_base/gforge/db/20021215.sql
   trunk/gforge_base/gforge/db/20021216.sql
   trunk/gforge_base/gforge/db/20021223-drops.sql
   trunk/gforge_base/gforge/db/20021223.sql
   trunk/gforge_base/gforge/db/20021230.sql
   trunk/gforge_base/gforge/db/20030102-drops.sql
   trunk/gforge_base/gforge/db/20030102.sql
   trunk/gforge_base/gforge/db/20030105.sql
   trunk/gforge_base/gforge/db/20030107.sql
   trunk/gforge_base/gforge/db/20030109.sql
   trunk/gforge_base/gforge/db/20030112.sql
   trunk/gforge_base/gforge/db/20030113-drops.sql
   trunk/gforge_base/gforge/db/20030113.sql
   trunk/gforge_base/gforge/db/20030115.sql
   trunk/gforge_base/gforge/db/20030131.sql
   trunk/gforge_base/gforge/db/20030209.sql
   trunk/gforge_base/gforge/db/20030312.sql
   trunk/gforge_base/gforge/db/20030513.sql
   trunk/gforge_base/gforge/db/20030701-gforge-3.0
   trunk/gforge_base/gforge/db/20030822.sql
   trunk/gforge_base/gforge/db/20031026-gforge-3.1
   trunk/gforge_base/gforge/db/20031105.sql
   trunk/gforge_base/gforge/db/20031124.sql
   trunk/gforge_base/gforge/db/20031126.sql
   trunk/gforge_base/gforge/db/20031129.sql
   trunk/gforge_base/gforge/db/20031205.sql
   trunk/gforge_base/gforge/db/20040108-gforge-3.21
   trunk/gforge_base/gforge/db/20040130.sql
   trunk/gforge_base/gforge/db/20040204.sql
   trunk/gforge_base/gforge/db/20040315.sql
   trunk/gforge_base/gforge/db/200403251.sql
   trunk/gforge_base/gforge/db/200403252.sql
   trunk/gforge_base/gforge/db/20040326-gforge-3.3
   trunk/gforge_base/gforge/db/20040329.sql
   trunk/gforge_base/gforge/db/20040507.sql
   trunk/gforge_base/gforge/db/20040722.sql
   trunk/gforge_base/gforge/db/20040729.sql
   trunk/gforge_base/gforge/db/20040804.sql
   trunk/gforge_base/gforge/db/20040826.sql
   trunk/gforge_base/gforge/db/20040826_migrateforum.php
   trunk/gforge_base/gforge/db/20040826_migraterbac.php
   trunk/gforge_base/gforge/db/20040914.sql
   trunk/gforge_base/gforge/db/20041001.sql
   trunk/gforge_base/gforge/db/20041005.sql
   trunk/gforge_base/gforge/db/20041006.sql
   trunk/gforge_base/gforge/db/20041014.sql
   trunk/gforge_base/gforge/db/20041020.sql
   trunk/gforge_base/gforge/db/20041025-gforge-4.0
   trunk/gforge_base/gforge/db/20041031.sql
   trunk/gforge_base/gforge/db/20041104.sql
   trunk/gforge_base/gforge/db/20041107-gforge-4.0.1
   trunk/gforge_base/gforge/db/20041108.sql
   trunk/gforge_base/gforge/db/20041124.sql
   trunk/gforge_base/gforge/db/20041211-syncmail.php
   trunk/gforge_base/gforge/db/20041215-gforge-4.0.2
   trunk/gforge_base/gforge/db/20041222-1-delete-task-artifact.php
   trunk/gforge_base/gforge/db/20041222-2.sql
   trunk/gforge_base/gforge/db/20041222-debian.sql
   trunk/gforge_base/gforge/db/20050115.sql
   trunk/gforge_base/gforge/db/20050127-frs-reorg.php
   trunk/gforge_base/gforge/db/20050130.sql
   trunk/gforge_base/gforge/db/20050212.sql
   trunk/gforge_base/gforge/db/20050214-nss.sql
   trunk/gforge_base/gforge/db/20050224-drop.sql
   trunk/gforge_base/gforge/db/20050224.sql
   trunk/gforge_base/gforge/db/20050225-nsssetup.sql
   trunk/gforge_base/gforge/db/20050227.sql
   trunk/gforge_base/gforge/db/20050311.sql
   trunk/gforge_base/gforge/db/20050315-drop.sql
   trunk/gforge_base/gforge/db/20050315.sql
   trunk/gforge_base/gforge/db/20050325-1-drop.sql
   trunk/gforge_base/gforge/db/20050325-1.sql
   trunk/gforge_base/gforge/db/20050325-2.php
   trunk/gforge_base/gforge/db/20050325-3-drop.sql
   trunk/gforge_base/gforge/db/20050325-3.sql
   trunk/gforge_base/gforge/db/20050605.sql
   trunk/gforge_base/gforge/db/20050617.php
   trunk/gforge_base/gforge/db/20050628.sql
   trunk/gforge_base/gforge/db/20050711.sql
   trunk/gforge_base/gforge/db/20050906.sql
   trunk/gforge_base/gforge/db/20051003.sql
   trunk/gforge_base/gforge/db/20051027-1.sql
   trunk/gforge_base/gforge/db/20051027-2.php
   trunk/gforge_base/gforge/db/FTI-20050315.sql
   trunk/gforge_base/gforge/db/FTI-20050401.sql
   trunk/gforge_base/gforge/db/FTI-20050530.sql
   trunk/gforge_base/gforge/db/FTI-20060130.sql
   trunk/gforge_base/gforge/db/FTI.sql
   trunk/gforge_base/gforge/db/README
   trunk/gforge_base/gforge/db/SQL_2.5/
   trunk/gforge_base/gforge/db/SQL_2.5/DefaultValues_2_5.sql
   trunk/gforge_base/gforge/db/SQL_2.5/SourceForge_2_5.sql
   trunk/gforge_base/gforge/db/SQL_2.6/
   trunk/gforge_base/gforge/db/SQL_2.6/DefaultValues.sql
   trunk/gforge_base/gforge/db/SQL_2.6/SourceForge.sql
   trunk/gforge_base/gforge/db/SQL_2.6/dbusers.sql
   trunk/gforge_base/gforge/db/SQL_2.6/languages.tab
   trunk/gforge_base/gforge/db/SQL_2.6/replicate.sh
   trunk/gforge_base/gforge/db/SQL_2.6/replication-master.sql
   trunk/gforge_base/gforge/db/SQL_2.6/replication-reset-master.sql
   trunk/gforge_base/gforge/db/SQL_2.6/replication-reset-slave.sql
   trunk/gforge_base/gforge/db/SQL_2.6/replication-slave.sql
   trunk/gforge_base/gforge/db/SQL_2.6/replication.plan
   trunk/gforge_base/gforge/db/SQL_2.6/trove_cat.tab
   trunk/gforge_base/gforge/db/SQL_2.6/user_rating.sql
   trunk/gforge_base/gforge/db/gforge-pgsql7.3.sql
   trunk/gforge_base/gforge/db/gforge.sql
   trunk/gforge_base/gforge/db/gforge2.6.sql
   trunk/gforge_base/gforge/db/nss-pgsql.sql
   trunk/gforge_base/gforge/db/timetracking-init.sql
   trunk/gforge_base/gforge/deb-specific/
   trunk/gforge_base/gforge/deb-specific/create-mailing-lists.pl
   trunk/gforge_base/gforge/deb-specific/create-vhosts.sh
   trunk/gforge_base/gforge/deb-specific/cvs-pserver
   trunk/gforge_base/gforge/deb-specific/cvssh.pl
   trunk/gforge_base/gforge/deb-specific/db-convert-to-unicode.pl
   trunk/gforge_base/gforge/deb-specific/db-upgrade.pl
   trunk/gforge_base/gforge/deb-specific/dns.head.template
   trunk/gforge_base/gforge/deb-specific/dns.simple.template
   trunk/gforge_base/gforge/deb-specific/dns_conf.pl
   trunk/gforge_base/gforge/deb-specific/dsf-helper.pl
   trunk/gforge_base/gforge/deb-specific/exim.directors.template
   trunk/gforge_base/gforge/deb-specific/fileforge.pl
   trunk/gforge_base/gforge/deb-specific/fix-frs.pl
   trunk/gforge_base/gforge/deb-specific/fix-mailing-lists.pl
   trunk/gforge_base/gforge/deb-specific/get_news_notapproved.pl
   trunk/gforge_base/gforge/deb-specific/gforge-config
   trunk/gforge_base/gforge/deb-specific/gforge-inject.pl
   trunk/gforge_base/gforge/deb-specific/gforge.schema
   trunk/gforge_base/gforge/deb-specific/group_dump_update.pl
   trunk/gforge_base/gforge/deb-specific/index_std.php
   trunk/gforge_base/gforge/deb-specific/init-extra.sql
   trunk/gforge_base/gforge/deb-specific/init-sequences.sql
   trunk/gforge_base/gforge/deb-specific/install-chroot.sh
   trunk/gforge_base/gforge/deb-specific/install-db.sh
   trunk/gforge_base/gforge/deb-specific/install-ftp.sh
   trunk/gforge_base/gforge/deb-specific/install-nsspgsql.sh
   trunk/gforge_base/gforge/deb-specific/install-postfix.sh
   trunk/gforge_base/gforge/deb-specific/install-skel.sh
   trunk/gforge_base/gforge/deb-specific/install-ssh.sh
   trunk/gforge_base/gforge/deb-specific/manage-uufiles.sh
   trunk/gforge_base/gforge/deb-specific/prepare-vhosts-file.pl
   trunk/gforge_base/gforge/deb-specific/register-plugin
   trunk/gforge_base/gforge/deb-specific/register-theme
   trunk/gforge_base/gforge/deb-specific/sf-2.6-complete.sql
   trunk/gforge_base/gforge/deb-specific/sf-add-skill
   trunk/gforge_base/gforge/deb-specific/sf-proftpd.conf
   trunk/gforge_base/gforge/deb-specific/sf2.5-to-sf2.6-extra.sql
   trunk/gforge_base/gforge/deb-specific/sf2.5-to-sf2.6.sql
   trunk/gforge_base/gforge/deb-specific/sqlhelper.pm
   trunk/gforge_base/gforge/deb-specific/sqlparser.pm
   trunk/gforge_base/gforge/deb-specific/ssh_dump_update.pl
   trunk/gforge_base/gforge/deb-specific/stats_cvs.pl
   trunk/gforge_base/gforge/deb-specific/stats_projects_logparse.pl
   trunk/gforge_base/gforge/deb-specific/tmp-sf.sql
   trunk/gforge_base/gforge/deb-specific/unregister-plugin
   trunk/gforge_base/gforge/deb-specific/unregister-theme
   trunk/gforge_base/gforge/deb-specific/update-user-group-cvs.sh
   trunk/gforge_base/gforge/deb-specific/user_dump_update.pl
   trunk/gforge_base/gforge/deb-specific/view_bug.sql
   trunk/gforge_base/gforge/deb-specific/view_patch.sql
   trunk/gforge_base/gforge/debian/
   trunk/gforge_base/gforge/debian/BUGS
   trunk/gforge_base/gforge/debian/README.Debian
   trunk/gforge_base/gforge/debian/README.Maintainer
   trunk/gforge_base/gforge/debian/README.Multipack
   trunk/gforge_base/gforge/debian/TODO
   trunk/gforge_base/gforge/debian/changelog
   trunk/gforge_base/gforge/debian/control
   trunk/gforge_base/gforge/debian/copyright
   trunk/gforge_base/gforge/debian/cvssh.sgml
   trunk/gforge_base/gforge/debian/dsf-helper/
   trunk/gforge_base/gforge/debian/dsf-helper/common-variables.config
   trunk/gforge_base/gforge/debian/dsf-helper/common-variables.templates
   trunk/gforge_base/gforge/debian/dsf-helper/create-random-pw.config
   trunk/gforge_base/gforge/debian/dsf-helper/create-random-pw.postinst
   trunk/gforge_base/gforge/debian/dsf-helper/dbhost-variables.config
   trunk/gforge_base/gforge/debian/dsf-helper/dbhost-variables.templates
   trunk/gforge_base/gforge/debian/dsf-helper/dbpasswd-variables.config
   trunk/gforge_base/gforge/debian/dsf-helper/dbpasswd-variables.templates
   trunk/gforge_base/gforge/debian/dsf-helper/downloadhost-variables.config
   trunk/gforge_base/gforge/debian/dsf-helper/downloadhost-variables.templates
   trunk/gforge_base/gforge/debian/dsf-helper/ftpuploadhost-variables.config
   trunk/gforge_base/gforge/debian/dsf-helper/ftpuploadhost-variables.templates
   trunk/gforge_base/gforge/debian/dsf-helper/get-pw-from-debconf.config
   trunk/gforge_base/gforge/debian/dsf-helper/groupid-variables.config
   trunk/gforge_base/gforge/debian/dsf-helper/groupid-variables.templates
   trunk/gforge_base/gforge/debian/dsf-helper/handle-mainconffile.config
   trunk/gforge_base/gforge/debian/dsf-helper/handle-mainconffile.postinst
   trunk/gforge_base/gforge/debian/dsf-helper/handle-mainconffile.postrm
   trunk/gforge_base/gforge/debian/dsf-helper/host-variables.config
   trunk/gforge_base/gforge/debian/dsf-helper/host-variables.templates
   trunk/gforge_base/gforge/debian/dsf-helper/jabberhost-variables.config
   trunk/gforge_base/gforge/debian/dsf-helper/jabberhost-variables.templates
   trunk/gforge_base/gforge/debian/dsf-helper/lists-variables.config
   trunk/gforge_base/gforge/debian/dsf-helper/lists-variables.templates
   trunk/gforge_base/gforge/debian/dsf-helper/replace-files.config
   trunk/gforge_base/gforge/debian/dsf-helper/replace-files.postinst
   trunk/gforge_base/gforge/debian/dsf-helper/replace-files.prerm
   trunk/gforge_base/gforge/debian/dsf-helper/replace-files.templates
   trunk/gforge_base/gforge/debian/dsf-helper/shellhost-variables.config
   trunk/gforge_base/gforge/debian/dsf-helper/shellhost-variables.templates
   trunk/gforge_base/gforge/debian/dsf-helper/uploadhost-variables.config
   trunk/gforge_base/gforge/debian/dsf-helper/uploadhost-variables.templates
   trunk/gforge_base/gforge/debian/dsf-helper/users-variables.config
   trunk/gforge_base/gforge/debian/dsf-helper/users-variables.templates
   trunk/gforge_base/gforge/debian/dsf-helper/web-variables.config
   trunk/gforge_base/gforge/debian/dsf-helper/web-variables.templates
   trunk/gforge_base/gforge/debian/gforge-common.config.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-common.dirs
   trunk/gforge_base/gforge/debian/gforge-common.docs
   trunk/gforge_base/gforge/debian/gforge-common.manpages
   trunk/gforge_base/gforge/debian/gforge-common.postinst.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-common.postrm.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-common.prerm
   trunk/gforge_base/gforge/debian/gforge-common.templates.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-config.sgml
   trunk/gforge_base/gforge/debian/gforge-db-postgresql.config.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-db-postgresql.cron.d
   trunk/gforge_base/gforge/debian/gforge-db-postgresql.dirs
   trunk/gforge_base/gforge/debian/gforge-db-postgresql.docs
   trunk/gforge_base/gforge/debian/gforge-db-postgresql.overrides
   trunk/gforge_base/gforge/debian/gforge-db-postgresql.postinst.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-db-postgresql.prerm.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-db-postgresql.templates.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-ftp-proftpd.config.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-ftp-proftpd.cron.d
   trunk/gforge_base/gforge/debian/gforge-ftp-proftpd.dirs
   trunk/gforge_base/gforge/debian/gforge-ftp-proftpd.docs
   trunk/gforge_base/gforge/debian/gforge-ftp-proftpd.postinst.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-ftp-proftpd.prerm.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-ftp-proftpd.templates.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-lists-mailman.cron.d
   trunk/gforge_base/gforge/debian/gforge-lists-mailman.dirs
   trunk/gforge_base/gforge/debian/gforge-lists-mailman.docs
   trunk/gforge_base/gforge/debian/gforge-lists-mailman.postinst
   trunk/gforge_base/gforge/debian/gforge-mta-postfix.config.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-mta-postfix.dirs
   trunk/gforge_base/gforge/debian/gforge-mta-postfix.docs
   trunk/gforge_base/gforge/debian/gforge-mta-postfix.postinst.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-mta-postfix.prerm.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-mta-postfix.templates.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-shell-postgresql.config.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-shell-postgresql.cron.d
   trunk/gforge_base/gforge/debian/gforge-shell-postgresql.dirs
   trunk/gforge_base/gforge/debian/gforge-shell-postgresql.docs
   trunk/gforge_base/gforge/debian/gforge-shell-postgresql.postinst.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-shell-postgresql.prerm.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-web-apache.config.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-web-apache.cron.d
   trunk/gforge_base/gforge/debian/gforge-web-apache.dirs
   trunk/gforge_base/gforge/debian/gforge-web-apache.docs
   trunk/gforge_base/gforge/debian/gforge-web-apache.overrides
   trunk/gforge_base/gforge/debian/gforge-web-apache.postinst.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-web-apache.prerm.dsfh-in
   trunk/gforge_base/gforge/debian/gforge-web-apache.templates.dsfh-in
   trunk/gforge_base/gforge/debian/gforge.docs
   trunk/gforge_base/gforge/debian/patches/
   trunk/gforge_base/gforge/debian/patches/00list
   trunk/gforge_base/gforge/debian/patches/00template
   trunk/gforge_base/gforge/debian/patches/add-groupadminmenu-hook.dpatch
   trunk/gforge_base/gforge/debian/patches/add-project-before-description-hook.dpatch
   trunk/gforge_base/gforge/debian/patches/disable-tracker-reply-by-email.dpatch
   trunk/gforge_base/gforge/debian/patches/globalsoff.dpatch
   trunk/gforge_base/gforge/debian/patches/missing-images.dpatch
   trunk/gforge_base/gforge/debian/po/
   trunk/gforge_base/gforge/debian/po/POTFILES.in
   trunk/gforge_base/gforge/debian/po/cs.po
   trunk/gforge_base/gforge/debian/po/de.po
   trunk/gforge_base/gforge/debian/po/fr.po
   trunk/gforge_base/gforge/debian/po/gl.po
   trunk/gforge_base/gforge/debian/po/nl.po
   trunk/gforge_base/gforge/debian/po/pt.po
   trunk/gforge_base/gforge/debian/po/sv.po
   trunk/gforge_base/gforge/debian/po/templates.pot
   trunk/gforge_base/gforge/debian/po/vi.po
   trunk/gforge_base/gforge/debian/rules
   trunk/gforge_base/gforge/docs/
   trunk/gforge_base/gforge/docs/README.ConvertToUTF8
   trunk/gforge_base/gforge/docs/README.Custom
   trunk/gforge_base/gforge/docs/README.KnownBugs
   trunk/gforge_base/gforge/docs/README.Plugins
   trunk/gforge_base/gforge/docs/README.Soap
   trunk/gforge_base/gforge/docs/README.Themes
   trunk/gforge_base/gforge/docs/README.TuningLDAP
   trunk/gforge_base/gforge/docs/architecture/
   trunk/gforge_base/gforge/docs/architecture/stats/
   trunk/gforge_base/gforge/docs/architecture/stats/stats-process.sda
   trunk/gforge_base/gforge/docs/debian-guide.html
   trunk/gforge_base/gforge/docs/debian-installguidefornewbies.html
   trunk/gforge_base/gforge/docs/doc_utils.php
   trunk/gforge_base/gforge/docs/docbook/
   trunk/gforge_base/gforge/docs/docbook/INSTALL
   trunk/gforge_base/gforge/docs/docbook/Makefile
   trunk/gforge_base/gforge/docs/docbook/README
   trunk/gforge_base/gforge/docs/docbook/build/
   trunk/gforge_base/gforge/docs/docbook/build/.keepme
   trunk/gforge_base/gforge/docs/docbook/docbook/
   trunk/gforge_base/gforge/docs/docbook/docbook/administration_guide/
   trunk/gforge_base/gforge/docs/docbook/docbook/administration_guide/administration_guide.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/contribution_guide/
   trunk/gforge_base/gforge/docs/docbook/docbook/contribution_guide/contribution_guide.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/contribution_guide/include/
   trunk/gforge_base/gforge/docs/docbook/docbook/contribution_guide/include/coding_standards.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/contribution_guide/include/cvs_repository.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/contribution_guide/include/howto_contribute.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/contribution_guide/include/howto_documentation.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/contribution_guide/include/howto_localization.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/contribution_guide/include/howto_xhtml.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/contribution_guide/include/templating.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/entities/
   trunk/gforge_base/gforge/docs/docbook/docbook/entities/authors.ent
   trunk/gforge_base/gforge/docs/docbook/docbook/entities/authors/
   trunk/gforge_base/gforge/docs/docbook/docbook/entities/authors/guillaume_smet.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/entities/authors/ken_mccullagh.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/entities/authors/ognyan_kulev.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/entities/authors/reinhard_spisser.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/entities/authors/roland_mas.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/entities/authors/tim_perdue.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/entities/authors/tom_copeland.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/entities/xinclude.ent
   trunk/gforge_base/gforge/docs/docbook/docbook/gforge_manual.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/installation_guide/
   trunk/gforge_base/gforge/docs/docbook/docbook/installation_guide/installation_guide.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/introduction/
   trunk/gforge_base/gforge/docs/docbook/docbook/introduction/introduction.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/getting_started/
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/getting_started/getting_started.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/introduction/
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/introduction/introduction.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/project_functions/
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/project_functions/cvs.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/project_functions/docman.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/project_functions/file_releases.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/project_functions/forums.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/project_functions/index.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/project_functions/mailing_lists.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/project_functions/news.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/project_functions/project_admin.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/project_functions/project_summary.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/project_functions/surveys.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/project_functions/task_manager.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/project_functions/tracker.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/sitewide_functions/
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/sitewide_functions/index.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/sitewide_functions/project_help.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/sitewide_functions/search.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/sitewide_functions/snippet.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/sitewide_functions/trove.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/user_functions/
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/user_functions/index.xml
   trunk/gforge_base/gforge/docs/docbook/docbook/user_guide/user_guide.xml
   trunk/gforge_base/gforge/docs/docbook/xsl/
   trunk/gforge_base/gforge/docs/docbook/xsl/article_html.xsl
   trunk/gforge_base/gforge/docs/docbook/xsl/article_html_chunk.xsl
   trunk/gforge_base/gforge/docs/docbook/xsl/article_pdf.xsl
   trunk/gforge_base/gforge/docs/docbook/xsl/book_html.xsl
   trunk/gforge_base/gforge/docs/docbook/xsl/book_html_chunk.xsl
   trunk/gforge_base/gforge/docs/docbook/xsl/book_pdf.xsl
   trunk/gforge_base/gforge/docs/docbook/xsl/include/
   trunk/gforge_base/gforge/docs/docbook/xsl/include/common_html.xsl
   trunk/gforge_base/gforge/docs/docbook/xsl/include/common_html_chunk.xsl
   trunk/gforge_base/gforge/docs/docbook/xsl/include/common_pdf.xsl
   trunk/gforge_base/gforge/docs/gforge-themes-HOWTO.html
   trunk/gforge_base/gforge/docs/images/
   trunk/gforge_base/gforge/docs/images/sflogo2-105a.png
   trunk/gforge_base/gforge/docs/index.php
   trunk/gforge_base/gforge/docs/log_formats.txt
   trunk/gforge_base/gforge/docs/migrating-to-gforge-HOWTO.html
   trunk/gforge_base/gforge/docs/migrating-to-gforge-REPORT.txt
   trunk/gforge_base/gforge/docs/phpdoc/
   trunk/gforge_base/gforge/docs/phpdoc/makedoc.sh
   trunk/gforge_base/gforge/docs/phpdoc/manageclass.patch
   trunk/gforge_base/gforge/docs/webalizer-HOWTO.html
   trunk/gforge_base/gforge/etc/
   trunk/gforge_base/gforge/etc/database.inc.example
   trunk/gforge_base/gforge/etc/gforge-httpd.conf.example
   trunk/gforge_base/gforge/etc/gforge-multi-host-httpd.conf.example
   trunk/gforge_base/gforge/etc/gforge.conf.example
   trunk/gforge_base/gforge/etc/httpd.conf.example
   trunk/gforge_base/gforge/etc/httpd.d/
   trunk/gforge_base/gforge/etc/httpd.d/00listen80
   trunk/gforge_base/gforge/etc/httpd.d/01common
   trunk/gforge_base/gforge/etc/httpd.d/05maindirauth
   trunk/gforge_base/gforge/etc/httpd.d/06maindirhttp
   trunk/gforge_base/gforge/etc/httpd.d/07maindirhttp.ssl
   trunk/gforge_base/gforge/etc/httpd.d/10scmauth
   trunk/gforge_base/gforge/etc/httpd.d/11scm00http
   trunk/gforge_base/gforge/etc/httpd.d/11scm99http
   trunk/gforge_base/gforge/etc/httpd.d/12scm00http.ssl
   trunk/gforge_base/gforge/etc/httpd.d/12scm99http.ssl
   trunk/gforge_base/gforge/etc/httpd.d/15download
   trunk/gforge_base/gforge/etc/httpd.d/20list
   trunk/gforge_base/gforge/etc/httpd.d/20listmailman
   trunk/gforge_base/gforge/etc/httpd.d/20listmailman.ssl
   trunk/gforge_base/gforge/etc/httpd.d/40virtualhost
   trunk/gforge_base/gforge/etc/httpd.d/55vhost
   trunk/gforge_base/gforge/etc/httpd.d/60plugin
   trunk/gforge_base/gforge/etc/httpd.d/README
   trunk/gforge_base/gforge/etc/httpd.d/httpd.secrets
   trunk/gforge_base/gforge/etc/httpd.d/lsttemplates
   trunk/gforge_base/gforge/etc/httpd.secrets.example
   trunk/gforge_base/gforge/etc/local.d/
   trunk/gforge_base/gforge/etc/local.d/01begin
   trunk/gforge_base/gforge/etc/local.d/02scm
   trunk/gforge_base/gforge/etc/local.d/10database.env
   trunk/gforge_base/gforge/etc/local.d/10database.simple
   trunk/gforge_base/gforge/etc/local.d/15system
   trunk/gforge_base/gforge/etc/local.d/20ldap
   trunk/gforge_base/gforge/etc/local.d/23jabber
   trunk/gforge_base/gforge/etc/local.d/25features
   trunk/gforge_base/gforge/etc/local.d/30homegroupother
   trunk/gforge_base/gforge/etc/local.d/35localizationcaching
   trunk/gforge_base/gforge/etc/local.d/50plugins
   trunk/gforge_base/gforge/etc/local.d/99end
   trunk/gforge_base/gforge/etc/local.inc.example
   trunk/gforge_base/gforge/etc/local.pl.example
   trunk/gforge_base/gforge/etc/nss-pgsql.conf.example
   trunk/gforge_base/gforge/etc/templates/
   trunk/gforge_base/gforge/etc/templates/database.inc
   trunk/gforge_base/gforge/etc/templates/httpd.vhosts
   trunk/gforge_base/gforge/etc/templates/local.pl
   trunk/gforge_base/gforge/monitor/
   trunk/gforge_base/gforge/monitor/check-system.pl
   trunk/gforge_base/gforge/monitor/systemdaemon
   trunk/gforge_base/gforge/plugins/
   trunk/gforge_base/gforge/plugins/cvssyncmail/
   trunk/gforge_base/gforge/plugins/cvssyncmail/INSTALL
   trunk/gforge_base/gforge/plugins/cvssyncmail/common/
   trunk/gforge_base/gforge/plugins/cvssyncmail/common/CVSSyncMailPlugin.class
   trunk/gforge_base/gforge/plugins/cvssyncmail/common/cvssyncmail-init.php
   trunk/gforge_base/gforge/plugins/cvssyncmail/common/languages/
   trunk/gforge_base/gforge/plugins/cvssyncmail/common/languages/Base.tab
   trunk/gforge_base/gforge/plugins/cvssyncmail/db/
   trunk/gforge_base/gforge/plugins/cvssyncmail/db/cvssyncmail-init.sql
   trunk/gforge_base/gforge/plugins/cvssyncmail/include/
   trunk/gforge_base/gforge/plugins/cvssyncmail/include/cvssyncmail-init.php
   trunk/gforge_base/gforge/plugins/cvstracker/
   trunk/gforge_base/gforge/plugins/cvstracker/AUTHORS
   trunk/gforge_base/gforge/plugins/cvstracker/COPYING
   trunk/gforge_base/gforge/plugins/cvstracker/Makefile
   trunk/gforge_base/gforge/plugins/cvstracker/README
   trunk/gforge_base/gforge/plugins/cvstracker/bin/
   trunk/gforge_base/gforge/plugins/cvstracker/bin/db-delete.pl
   trunk/gforge_base/gforge/plugins/cvstracker/bin/db-upgrade.pl
   trunk/gforge_base/gforge/plugins/cvstracker/bin/post.php
   trunk/gforge_base/gforge/plugins/cvstracker/bin/update_loginfo.php
   trunk/gforge_base/gforge/plugins/cvstracker/debian/
   trunk/gforge_base/gforge/plugins/cvstracker/debian/README.Debian
   trunk/gforge_base/gforge/plugins/cvstracker/debian/changelog
   trunk/gforge_base/gforge/plugins/cvstracker/debian/control
   trunk/gforge_base/gforge/plugins/cvstracker/debian/copyright
   trunk/gforge_base/gforge/plugins/cvstracker/debian/cron.d
   trunk/gforge_base/gforge/plugins/cvstracker/debian/dirs
   trunk/gforge_base/gforge/plugins/cvstracker/debian/postinst
   trunk/gforge_base/gforge/plugins/cvstracker/debian/prerm
   trunk/gforge_base/gforge/plugins/cvstracker/debian/rules
   trunk/gforge_base/gforge/plugins/cvstracker/etc/
   trunk/gforge_base/gforge/plugins/cvstracker/etc/plugins/
   trunk/gforge_base/gforge/plugins/cvstracker/etc/plugins/cvstracker/
   trunk/gforge_base/gforge/plugins/cvstracker/etc/plugins/cvstracker/config.php
   trunk/gforge_base/gforge/plugins/cvstracker/etc/plugins/cvstracker/cvstracker.conf
   trunk/gforge_base/gforge/plugins/cvstracker/gforge-plugin-cvstracker.spec
   trunk/gforge_base/gforge/plugins/cvstracker/httpd.conf
   trunk/gforge_base/gforge/plugins/cvstracker/include/
   trunk/gforge_base/gforge/plugins/cvstracker/include/Snoopy.class
   trunk/gforge_base/gforge/plugins/cvstracker/include/cvstracker-init.php
   trunk/gforge_base/gforge/plugins/cvstracker/include/cvstrackerPlugin.class
   trunk/gforge_base/gforge/plugins/cvstracker/include/languages/
   trunk/gforge_base/gforge/plugins/cvstracker/include/languages/Base.tab
   trunk/gforge_base/gforge/plugins/cvstracker/include/languages/French.tab
   trunk/gforge_base/gforge/plugins/cvstracker/include/languages/Spanish.tab
   trunk/gforge_base/gforge/plugins/cvstracker/lib/
   trunk/gforge_base/gforge/plugins/cvstracker/lib/20050305.sql
   trunk/gforge_base/gforge/plugins/cvstracker/lib/20051003.sql
   trunk/gforge_base/gforge/plugins/cvstracker/lib/cvstracker-init.sql
   trunk/gforge_base/gforge/plugins/cvstracker/rpm-specific/
   trunk/gforge_base/gforge/plugins/cvstracker/rpm-specific/cron.d/
   trunk/gforge_base/gforge/plugins/cvstracker/rpm-specific/cron.d/gforge-plugin-cvstracker
   trunk/gforge_base/gforge/plugins/cvstracker/www/
   trunk/gforge_base/gforge/plugins/cvstracker/www/newcommit.php
   trunk/gforge_base/gforge/rpm-specific/
   trunk/gforge_base/gforge/rpm-specific/conf/
   trunk/gforge_base/gforge/rpm-specific/conf/gforge.conf
   trunk/gforge_base/gforge/rpm-specific/conf/vhost.conf
   trunk/gforge_base/gforge/rpm-specific/cron.d/
   trunk/gforge_base/gforge/rpm-specific/cron.d/gforge
   trunk/gforge_base/gforge/rpm-specific/custom/
   trunk/gforge_base/gforge/rpm-specific/custom/.keepme
   trunk/gforge_base/gforge/rpm-specific/languages/
   trunk/gforge_base/gforge/rpm-specific/languages/.keepme
   trunk/gforge_base/gforge/rpm-specific/patches/
   trunk/gforge_base/gforge/rpm-specific/patches/gforge-4.0-deb_rpm.patch
   trunk/gforge_base/gforge/rpm-specific/scripts/
   trunk/gforge_base/gforge/rpm-specific/scripts/gforge-config
   trunk/gforge_base/gforge/utils/
   trunk/gforge_base/gforge/utils/change_default_shell
   trunk/gforge_base/gforge/utils/cvs1/
   trunk/gforge_base/gforge/utils/cvs1/cvscreate.sh
   trunk/gforge_base/gforge/utils/cvs1/cvstar_genlist.pl
   trunk/gforge_base/gforge/utils/cvs1/cvstar_superscript.pl
   trunk/gforge_base/gforge/utils/decode_images.sh
   trunk/gforge_base/gforge/utils/fill-in-the-blanks.pl
   trunk/gforge_base/gforge/utils/fixscripts/
   trunk/gforge_base/gforge/utils/fixscripts/fix_broken_uids.php
   trunk/gforge_base/gforge/utils/fixscripts/fix_image_data.php
   trunk/gforge_base/gforge/utils/fixscripts/tools_data_cleanup.php
   trunk/gforge_base/gforge/utils/fixscripts/upgrade_bug_data.php
   trunk/gforge_base/gforge/utils/fixscripts/upgrade_filerelease_data.php
   trunk/gforge_base/gforge/utils/fixscripts/upgrade_forum_data.php
   trunk/gforge_base/gforge/utils/fixscripts/upgrade_task_data.php
   trunk/gforge_base/gforge/utils/grap.c
   trunk/gforge_base/gforge/utils/include.pl
   trunk/gforge_base/gforge/utils/install-apache.sh
   trunk/gforge_base/gforge/utils/ldap/
   trunk/gforge_base/gforge/utils/ldap/ldap-check-replica
   trunk/gforge_base/gforge/utils/ldap/ldap-clean
   trunk/gforge_base/gforge/utils/ldap/ldap-del-user
   trunk/gforge_base/gforge/utils/ldap/ldap-dump
   trunk/gforge_base/gforge/utils/ldap/ldap-import
   trunk/gforge_base/gforge/utils/ldap/sql2ldif.pl
   trunk/gforge_base/gforge/utils/sffingerd.c
   trunk/gforge_base/gforge/utils/underworld-dummy/
   trunk/gforge_base/gforge/utils/underworld-dummy/aliases.zone
   trunk/gforge_base/gforge/utils/underworld-dummy/dns_conf.pl
   trunk/gforge_base/gforge/utils/underworld-dummy/dump_database.pl
   trunk/gforge_base/gforge/utils/underworld-dummy/ia64_dump.pl
   trunk/gforge_base/gforge/utils/underworld-dummy/mail_aliases.pl
   trunk/gforge_base/gforge/utils/underworld-dummy/mailing_lists_dump.pl
   trunk/gforge_base/gforge/utils/underworld-dummy/new_aliases.pl
   trunk/gforge_base/gforge/utils/underworld-dummy/ssh_dump.pl
   trunk/gforge_base/gforge/www/
   trunk/gforge_base/gforge/www/404.php
   trunk/gforge_base/gforge/www/account/
   trunk/gforge_base/gforge/www/account/change_email-complete.php
   trunk/gforge_base/gforge/www/account/change_email.php
   trunk/gforge_base/gforge/www/account/change_pw.php
   trunk/gforge_base/gforge/www/account/editsshkeys.php
   trunk/gforge_base/gforge/www/account/first.php
   trunk/gforge_base/gforge/www/account/index.php
   trunk/gforge_base/gforge/www/account/login.php
   trunk/gforge_base/gforge/www/account/logout.php
   trunk/gforge_base/gforge/www/account/lostlogin.php
   trunk/gforge_base/gforge/www/account/lostpw.php
   trunk/gforge_base/gforge/www/account/pending-resend.php
   trunk/gforge_base/gforge/www/account/register.php
   trunk/gforge_base/gforge/www/account/setlang.php
   trunk/gforge_base/gforge/www/account/unsubscribe.php
   trunk/gforge_base/gforge/www/account/verify.php
   trunk/gforge_base/gforge/www/admin/
   trunk/gforge_base/gforge/www/admin/admin_table.php
   trunk/gforge_base/gforge/www/admin/admin_utils.php
   trunk/gforge_base/gforge/www/admin/approve-pending.php
   trunk/gforge_base/gforge/www/admin/configman.php
   trunk/gforge_base/gforge/www/admin/cronman.php
   trunk/gforge_base/gforge/www/admin/database.php
   trunk/gforge_base/gforge/www/admin/edit_frs_filetype.php
   trunk/gforge_base/gforge/www/admin/edit_frs_processor.php
   trunk/gforge_base/gforge/www/admin/edit_licenses.php
   trunk/gforge_base/gforge/www/admin/edit_supported_languages.php
   trunk/gforge_base/gforge/www/admin/edit_theme.php
   trunk/gforge_base/gforge/www/admin/groupdelete.php
   trunk/gforge_base/gforge/www/admin/groupedit.php
   trunk/gforge_base/gforge/www/admin/grouplist.php
   trunk/gforge_base/gforge/www/admin/index.php
   trunk/gforge_base/gforge/www/admin/languages/
   trunk/gforge_base/gforge/www/admin/languages/admintabfiles.php
   trunk/gforge_base/gforge/www/admin/languages/editdouble.php
   trunk/gforge_base/gforge/www/admin/languages/editnotinbasetabfiles.php
   trunk/gforge_base/gforge/www/admin/languages/editnotranstabfiles.php
   trunk/gforge_base/gforge/www/admin/languages/edittabfiles.php
   trunk/gforge_base/gforge/www/admin/languages/edittranstabfiles.php
   trunk/gforge_base/gforge/www/admin/languages/gettabfiles.php
   trunk/gforge_base/gforge/www/admin/languages/loadtabfiles.php
   trunk/gforge_base/gforge/www/admin/languages/seenotinbasetabfiles.php
   trunk/gforge_base/gforge/www/admin/languages/seenotranstabfiles.php
   trunk/gforge_base/gforge/www/admin/languages/seetabfiles.php
   trunk/gforge_base/gforge/www/admin/languages/seetranstabfiles.php
   trunk/gforge_base/gforge/www/admin/massmail.php
   trunk/gforge_base/gforge/www/admin/pluginman.php
   trunk/gforge_base/gforge/www/admin/responses_admin.php
   trunk/gforge_base/gforge/www/admin/search.php
   trunk/gforge_base/gforge/www/admin/trove/
   trunk/gforge_base/gforge/www/admin/trove/trove_cat_add.php
   trunk/gforge_base/gforge/www/admin/trove/trove_cat_edit.php
   trunk/gforge_base/gforge/www/admin/trove/trove_cat_list.php
   trunk/gforge_base/gforge/www/admin/unsubscribe.php
   trunk/gforge_base/gforge/www/admin/useredit.php
   trunk/gforge_base/gforge/www/admin/userlist.php
   trunk/gforge_base/gforge/www/admin/vhost.php
   trunk/gforge_base/gforge/www/dbimage.php
   trunk/gforge_base/gforge/www/developer/
   trunk/gforge_base/gforge/www/developer/diary.php
   trunk/gforge_base/gforge/www/developer/index.php
   trunk/gforge_base/gforge/www/developer/monitor.php
   trunk/gforge_base/gforge/www/docman/
   trunk/gforge_base/gforge/www/docman/admin/
   trunk/gforge_base/gforge/www/docman/admin/index.php
   trunk/gforge_base/gforge/www/docman/display_doc.php
   trunk/gforge_base/gforge/www/docman/include/
   trunk/gforge_base/gforge/www/docman/include/DocumentGroupHTML.class
   trunk/gforge_base/gforge/www/docman/include/doc_utils.php
   trunk/gforge_base/gforge/www/docman/index.php
   trunk/gforge_base/gforge/www/docman/new.php
   trunk/gforge_base/gforge/www/docman/view.php
   trunk/gforge_base/gforge/www/download.php
   trunk/gforge_base/gforge/www/export/
   trunk/gforge_base/gforge/www/export/forum.php
   trunk/gforge_base/gforge/www/export/forum_0.1.dtd
   trunk/gforge_base/gforge/www/export/index.php
   trunk/gforge_base/gforge/www/export/projhtml.php
   trunk/gforge_base/gforge/www/export/projnews.php
   trunk/gforge_base/gforge/www/export/projtitl.php
   trunk/gforge_base/gforge/www/export/rss20_activity.php
   trunk/gforge_base/gforge/www/export/rss20_docman.php
   trunk/gforge_base/gforge/www/export/rss20_newreleases.php
   trunk/gforge_base/gforge/www/export/rss20_news.php
   trunk/gforge_base/gforge/www/export/rss20_projects.php
   trunk/gforge_base/gforge/www/export/rss20_tracker.php
   trunk/gforge_base/gforge/www/export/rss_project.php
   trunk/gforge_base/gforge/www/export/rss_sfnewreleases.php
   trunk/gforge_base/gforge/www/export/rss_sfnews.php
   trunk/gforge_base/gforge/www/export/rss_sfprojects.php
   trunk/gforge_base/gforge/www/export/rss_utils.inc
   trunk/gforge_base/gforge/www/export/tracker.php
   trunk/gforge_base/gforge/www/export/tracker.xsd
   trunk/gforge_base/gforge/www/export/trove_tree.php
   trunk/gforge_base/gforge/www/export/trove_tree_0.1.dtd
   trunk/gforge_base/gforge/www/favicon.ico
   trunk/gforge_base/gforge/www/forum/
   trunk/gforge_base/gforge/www/forum/admin/
   trunk/gforge_base/gforge/www/forum/admin/index.php
   trunk/gforge_base/gforge/www/forum/forum.php
   trunk/gforge_base/gforge/www/forum/include/
   trunk/gforge_base/gforge/www/forum/include/ForumHTML.class
   trunk/gforge_base/gforge/www/forum/index.php
   trunk/gforge_base/gforge/www/forum/message.php
   trunk/gforge_base/gforge/www/forum/monitor.php
   trunk/gforge_base/gforge/www/forum/new.php
   trunk/gforge_base/gforge/www/forum/save.php
   trunk/gforge_base/gforge/www/frs/
   trunk/gforge_base/gforge/www/frs/admin/
   trunk/gforge_base/gforge/www/frs/admin/deletepackage.php
   trunk/gforge_base/gforge/www/frs/admin/deleterelease.php
   trunk/gforge_base/gforge/www/frs/admin/editrelease.php
   trunk/gforge_base/gforge/www/frs/admin/index.php
   trunk/gforge_base/gforge/www/frs/admin/qrs.php
   trunk/gforge_base/gforge/www/frs/admin/showreleases.php
   trunk/gforge_base/gforge/www/frs/download.php
   trunk/gforge_base/gforge/www/frs/include/
   trunk/gforge_base/gforge/www/frs/include/frs_utils.php
   trunk/gforge_base/gforge/www/frs/index.php
   trunk/gforge_base/gforge/www/frs/monitor.php
   trunk/gforge_base/gforge/www/frs/shownotes.php
   trunk/gforge_base/gforge/www/gfginstaller.png
   trunk/gforge_base/gforge/www/help/
   trunk/gforge_base/gforge/www/help/index.php
   trunk/gforge_base/gforge/www/help/tracker.php
   trunk/gforge_base/gforge/www/help/trove_cat.php
   trunk/gforge_base/gforge/www/images/
   trunk/gforge_base/gforge/www/images/blank.png
   trunk/gforge_base/gforge/www/images/clear.png
   trunk/gforge_base/gforge/www/images/debian-sf-icon.png
   trunk/gforge_base/gforge/www/images/favicon.ico
   trunk/gforge_base/gforge/www/images/gantt.png
   trunk/gforge_base/gforge/www/images/gforge.jpg
   trunk/gforge_base/gforge/www/images/ic/
   trunk/gforge_base/gforge/www/images/ic/caret.png
   trunk/gforge_base/gforge/www/images/ic/cfolder15.png
   trunk/gforge_base/gforge/www/images/ic/check.png
   trunk/gforge_base/gforge/www/images/ic/cvs16b.png
   trunk/gforge_base/gforge/www/images/ic/docman16b.png
   trunk/gforge_base/gforge/www/images/ic/forum20g.png
   trunk/gforge_base/gforge/www/images/ic/forum20w.png
   trunk/gforge_base/gforge/www/images/ic/ftp16b.png
   trunk/gforge_base/gforge/www/images/ic/halfcheck.png
   trunk/gforge_base/gforge/www/images/ic/home16b.png
   trunk/gforge_base/gforge/www/images/ic/index.png
   trunk/gforge_base/gforge/www/images/ic/mail16b.png
   trunk/gforge_base/gforge/www/images/ic/mail16d.png
   trunk/gforge_base/gforge/www/images/ic/mail16w.png
   trunk/gforge_base/gforge/www/images/ic/manual16c.png
   trunk/gforge_base/gforge/www/images/ic/msg.png
   trunk/gforge_base/gforge/www/images/ic/notes16.png
   trunk/gforge_base/gforge/www/images/ic/ofolder15.png
   trunk/gforge_base/gforge/www/images/ic/pencil.png
   trunk/gforge_base/gforge/www/images/ic/save.png
   trunk/gforge_base/gforge/www/images/ic/support16b.jpg
   trunk/gforge_base/gforge/www/images/ic/survey16b.png
   trunk/gforge_base/gforge/www/images/ic/taskman16b.png
   trunk/gforge_base/gforge/www/images/ic/taskman20g.png
   trunk/gforge_base/gforge/www/images/ic/taskman20w.png
   trunk/gforge_base/gforge/www/images/ic/tracker20g.png
   trunk/gforge_base/gforge/www/images/ic/tracker20w.png
   trunk/gforge_base/gforge/www/images/ic/trash-x.png
   trunk/gforge_base/gforge/www/images/ic/trash.png
   trunk/gforge_base/gforge/www/images/ic/write16w.png
   trunk/gforge_base/gforge/www/images/ic/xmail16w.png
   trunk/gforge_base/gforge/www/images/icon.png
   trunk/gforge_base/gforge/www/images/msg.png
   trunk/gforge_base/gforge/www/images/pow-gforge.png
   trunk/gforge_base/gforge/www/images/sf-for-debian.png
   trunk/gforge_base/gforge/www/images/t.png
   trunk/gforge_base/gforge/www/images/t2.png
   trunk/gforge_base/gforge/www/impress/
   trunk/gforge_base/gforge/www/impress/index.php
   trunk/gforge_base/gforge/www/include/
   trunk/gforge_base/gforge/www/include/BaseLanguage.class
   trunk/gforge_base/gforge/www/include/HTML_Graphs.php
   trunk/gforge_base/gforge/www/include/Layout.class
   trunk/gforge_base/gforge/www/include/LayoutSF.class
   trunk/gforge_base/gforge/www/include/bookmarks.php
   trunk/gforge_base/gforge/www/include/browser.php
   trunk/gforge_base/gforge/www/include/canned_responses.php
   trunk/gforge_base/gforge/www/include/database-mysql.php
   trunk/gforge_base/gforge/www/include/database-oci8.php
   trunk/gforge_base/gforge/www/include/database-pgsql.php
   trunk/gforge_base/gforge/www/include/exit.php
   trunk/gforge_base/gforge/www/include/features_boxes.php
   trunk/gforge_base/gforge/www/include/filechecks.php
   trunk/gforge_base/gforge/www/include/graph_lib.php
   trunk/gforge_base/gforge/www/include/help.php
   trunk/gforge_base/gforge/www/include/html.php
   trunk/gforge_base/gforge/www/include/languages/
   trunk/gforge_base/gforge/www/include/languages/Base.tab
   trunk/gforge_base/gforge/www/include/languages/Basque.tab
   trunk/gforge_base/gforge/www/include/languages/Bulgarian.tab
   trunk/gforge_base/gforge/www/include/languages/Catalan.tab
   trunk/gforge_base/gforge/www/include/languages/Chinese.tab
   trunk/gforge_base/gforge/www/include/languages/Dutch.tab
   trunk/gforge_base/gforge/www/include/languages/English.tab
   trunk/gforge_base/gforge/www/include/languages/Esperanto.tab
   trunk/gforge_base/gforge/www/include/languages/French.tab
   trunk/gforge_base/gforge/www/include/languages/German.tab
   trunk/gforge_base/gforge/www/include/languages/Greek.tab
   trunk/gforge_base/gforge/www/include/languages/Hebrew.tab
   trunk/gforge_base/gforge/www/include/languages/Indonesian.tab
   trunk/gforge_base/gforge/www/include/languages/Italian.tab
   trunk/gforge_base/gforge/www/include/languages/Japanese.tab
   trunk/gforge_base/gforge/www/include/languages/Korean.tab
   trunk/gforge_base/gforge/www/include/languages/Latin.tab
   trunk/gforge_base/gforge/www/include/languages/Norwegian.tab
   trunk/gforge_base/gforge/www/include/languages/Polish.tab
   trunk/gforge_base/gforge/www/include/languages/Portuguese.tab
   trunk/gforge_base/gforge/www/include/languages/PortugueseBrazilian.tab
   trunk/gforge_base/gforge/www/include/languages/Russian.tab
   trunk/gforge_base/gforge/www/include/languages/SimplifiedChinese.tab
   trunk/gforge_base/gforge/www/include/languages/Spanish.tab
   trunk/gforge_base/gforge/www/include/languages/Swedish.tab
   trunk/gforge_base/gforge/www/include/languages/Thai.tab
   trunk/gforge_base/gforge/www/include/logger.php
   trunk/gforge_base/gforge/www/include/menuSF.php
   trunk/gforge_base/gforge/www/include/note.php
   trunk/gforge_base/gforge/www/include/pre.php
   trunk/gforge_base/gforge/www/include/project_home.php
   trunk/gforge_base/gforge/www/include/project_summary.php
   trunk/gforge_base/gforge/www/include/role_utils.php
   trunk/gforge_base/gforge/www/include/squal_exit.php
   trunk/gforge_base/gforge/www/include/squal_pre.php
   trunk/gforge_base/gforge/www/include/stats_function.php
   trunk/gforge_base/gforge/www/include/tool_reports.php
   trunk/gforge_base/gforge/www/include/trove.php
   trunk/gforge_base/gforge/www/include/user_home.php
   trunk/gforge_base/gforge/www/index.php
   trunk/gforge_base/gforge/www/index_std.php
   trunk/gforge_base/gforge/www/js/
   trunk/gforge_base/gforge/www/js/awstats_misc_tracker.js
   trunk/gforge_base/gforge/www/mail/
   trunk/gforge_base/gforge/www/mail/admin/
   trunk/gforge_base/gforge/www/mail/admin/deletelist.php
   trunk/gforge_base/gforge/www/mail/admin/index.php
   trunk/gforge_base/gforge/www/mail/index.php
   trunk/gforge_base/gforge/www/mail/mail_utils.php
   trunk/gforge_base/gforge/www/my/
   trunk/gforge_base/gforge/www/my/bookmark_add.php
   trunk/gforge_base/gforge/www/my/bookmark_delete.php
   trunk/gforge_base/gforge/www/my/bookmark_edit.php
   trunk/gforge_base/gforge/www/my/diary.php
   trunk/gforge_base/gforge/www/my/index.php
   trunk/gforge_base/gforge/www/my/rmproject.php
   trunk/gforge_base/gforge/www/new/
   trunk/gforge_base/gforge/www/new/index.php
   trunk/gforge_base/gforge/www/news/
   trunk/gforge_base/gforge/www/news/admin/
   trunk/gforge_base/gforge/www/news/admin/index.php
   trunk/gforge_base/gforge/www/news/admin/news_admin_utils.php
   trunk/gforge_base/gforge/www/news/index.php
   trunk/gforge_base/gforge/www/news/news_utils.php
   trunk/gforge_base/gforge/www/news/submit.php
   trunk/gforge_base/gforge/www/notepad.php
   trunk/gforge_base/gforge/www/people/
   trunk/gforge_base/gforge/www/people/admin/
   trunk/gforge_base/gforge/www/people/admin/index.php
   trunk/gforge_base/gforge/www/people/createjob.php
   trunk/gforge_base/gforge/www/people/editjob.php
   trunk/gforge_base/gforge/www/people/editprofile.php
   trunk/gforge_base/gforge/www/people/helpwanted-latest.php
   trunk/gforge_base/gforge/www/people/index.php
   trunk/gforge_base/gforge/www/people/people_utils.php
   trunk/gforge_base/gforge/www/people/skills_utils.php
   trunk/gforge_base/gforge/www/people/viewjob.php
   trunk/gforge_base/gforge/www/people/viewprofile.php
   trunk/gforge_base/gforge/www/pm/
   trunk/gforge_base/gforge/www/pm/add_task.php
   trunk/gforge_base/gforge/www/pm/admin/
   trunk/gforge_base/gforge/www/pm/admin/index.php
   trunk/gforge_base/gforge/www/pm/browse_task.php
   trunk/gforge_base/gforge/www/pm/calendar.php
   trunk/gforge_base/gforge/www/pm/deletetask.php
   trunk/gforge_base/gforge/www/pm/detail_task.php
   trunk/gforge_base/gforge/www/pm/downloadcsv.php
   trunk/gforge_base/gforge/www/pm/gantt.php
   trunk/gforge_base/gforge/www/pm/ganttofuser.php
   trunk/gforge_base/gforge/www/pm/ganttpage.php
   trunk/gforge_base/gforge/www/pm/include/
   trunk/gforge_base/gforge/www/pm/include/ProjectGroupHTML.class
   trunk/gforge_base/gforge/www/pm/include/ProjectTaskHTML.class
   trunk/gforge_base/gforge/www/pm/index.php
   trunk/gforge_base/gforge/www/pm/mod_task.php
   trunk/gforge_base/gforge/www/pm/msproject/
   trunk/gforge_base/gforge/www/pm/msproject/msp.php
   trunk/gforge_base/gforge/www/pm/msproject/xmlparser.php
   trunk/gforge_base/gforge/www/pm/postuploadcsv.php
   trunk/gforge_base/gforge/www/pm/reporting/
   trunk/gforge_base/gforge/www/pm/reporting/index.php
   trunk/gforge_base/gforge/www/pm/task.php
   trunk/gforge_base/gforge/www/pm/uploadcsv.php
   trunk/gforge_base/gforge/www/project/
   trunk/gforge_base/gforge/www/project/admin/
   trunk/gforge_base/gforge/www/project/admin/database.php
   trunk/gforge_base/gforge/www/project/admin/editgroupinfo.php
   trunk/gforge_base/gforge/www/project/admin/editimages.php
   trunk/gforge_base/gforge/www/project/admin/group_trove.php
   trunk/gforge_base/gforge/www/project/admin/history.php
   trunk/gforge_base/gforge/www/project/admin/index.php
   trunk/gforge_base/gforge/www/project/admin/massadd.php
   trunk/gforge_base/gforge/www/project/admin/massfinish.php
   trunk/gforge_base/gforge/www/project/admin/project_admin_utils.php
   trunk/gforge_base/gforge/www/project/admin/roleedit.php
   trunk/gforge_base/gforge/www/project/admin/vhost.php
   trunk/gforge_base/gforge/www/project/index.php
   trunk/gforge_base/gforge/www/project/memberlist.php
   trunk/gforge_base/gforge/www/project/request.php
   trunk/gforge_base/gforge/www/project/showfiles.php
   trunk/gforge_base/gforge/www/project/stats/
   trunk/gforge_base/gforge/www/project/stats/index.php
   trunk/gforge_base/gforge/www/project/stats/project_stats_utils.php
   trunk/gforge_base/gforge/www/projects
   trunk/gforge_base/gforge/www/register/
   trunk/gforge_base/gforge/www/register/index.php
   trunk/gforge_base/gforge/www/register/projectinfo.php
   trunk/gforge_base/gforge/www/reporting/
   trunk/gforge_base/gforge/www/reporting/groupadded.php
   trunk/gforge_base/gforge/www/reporting/groupadded_graph.php
   trunk/gforge_base/gforge/www/reporting/groupcum.php
   trunk/gforge_base/gforge/www/reporting/groupcum_graph.php
   trunk/gforge_base/gforge/www/reporting/index.php
   trunk/gforge_base/gforge/www/reporting/projectact.php
   trunk/gforge_base/gforge/www/reporting/projectact_graph.php
   trunk/gforge_base/gforge/www/reporting/projecttime.php
   trunk/gforge_base/gforge/www/reporting/projecttime_graph.php
   trunk/gforge_base/gforge/www/reporting/rebuild.php
   trunk/gforge_base/gforge/www/reporting/siteact.php
   trunk/gforge_base/gforge/www/reporting/siteact_graph.php
   trunk/gforge_base/gforge/www/reporting/sitetime.php
   trunk/gforge_base/gforge/www/reporting/sitetime_graph.php
   trunk/gforge_base/gforge/www/reporting/sitetimebar.php
   trunk/gforge_base/gforge/www/reporting/sitetimebar_graph.php
   trunk/gforge_base/gforge/www/reporting/timeadd.php
   trunk/gforge_base/gforge/www/reporting/timecategory.php
   trunk/gforge_base/gforge/www/reporting/toolspie.php
   trunk/gforge_base/gforge/www/reporting/toolspie_graph.php
   trunk/gforge_base/gforge/www/reporting/trackerpie_graph.php
   trunk/gforge_base/gforge/www/reporting/useract.php
   trunk/gforge_base/gforge/www/reporting/useract_graph.php
   trunk/gforge_base/gforge/www/reporting/useradded.php
   trunk/gforge_base/gforge/www/reporting/useradded_graph.php
   trunk/gforge_base/gforge/www/reporting/usercum.php
   trunk/gforge_base/gforge/www/reporting/usercum_graph.php
   trunk/gforge_base/gforge/www/reporting/usersummary.php
   trunk/gforge_base/gforge/www/reporting/usertime.php
   trunk/gforge_base/gforge/www/reporting/usertime_graph.php
   trunk/gforge_base/gforge/www/scm/
   trunk/gforge_base/gforge/www/scm/admin/
   trunk/gforge_base/gforge/www/scm/admin/index.php
   trunk/gforge_base/gforge/www/scm/include/
   trunk/gforge_base/gforge/www/scm/include/scm_utils.php
   trunk/gforge_base/gforge/www/scm/index.php
   trunk/gforge_base/gforge/www/search/
   trunk/gforge_base/gforge/www/search/advanced_search.php
   trunk/gforge_base/gforge/www/search/include/
   trunk/gforge_base/gforge/www/search/include/SearchManager.class
   trunk/gforge_base/gforge/www/search/include/engines/
   trunk/gforge_base/gforge/www/search/include/engines/ArtifactSearchEngine.class
   trunk/gforge_base/gforge/www/search/include/engines/DocsGroupSearchEngine.class
   trunk/gforge_base/gforge/www/search/include/engines/ForumSearchEngine.class
   trunk/gforge_base/gforge/www/search/include/engines/ForumsGroupSearchEngine.class
   trunk/gforge_base/gforge/www/search/include/engines/FrsGroupSearchEngine.class
   trunk/gforge_base/gforge/www/search/include/engines/GroupSearchEngine.class
   trunk/gforge_base/gforge/www/search/include/engines/NewsGroupSearchEngine.class
   trunk/gforge_base/gforge/www/search/include/engines/SearchEngine.class
   trunk/gforge_base/gforge/www/search/include/engines/TasksGroupSearchEngine.class
   trunk/gforge_base/gforge/www/search/include/engines/TrackersGroupSearchEngine.class
   trunk/gforge_base/gforge/www/search/include/renderers/
   trunk/gforge_base/gforge/www/search/include/renderers/AdvancedSearchHtmlSearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/ArtifactHtmlSearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/DocsHtmlSearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/ForumHtmlSearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/ForumsHtmlSearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/FrsHtmlSearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/FullProjectHtmlSearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/HtmlGroupSearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/HtmlSearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/NewsHtmlSearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/PeopleHtmlSearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/ProjectHtmlSearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/ProjectRssSearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/RssSearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/SearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/SkillHtmlSearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/TasksHtmlSearchRenderer.class
   trunk/gforge_base/gforge/www/search/include/renderers/TrackersHtmlSearchRenderer.class
   trunk/gforge_base/gforge/www/search/index.php
   trunk/gforge_base/gforge/www/sendmessage.php
   trunk/gforge_base/gforge/www/snapshots.php
   trunk/gforge_base/gforge/www/snippet/
   trunk/gforge_base/gforge/www/snippet/add_snippet_to_package.php
   trunk/gforge_base/gforge/www/snippet/addversion.php
   trunk/gforge_base/gforge/www/snippet/browse.php
   trunk/gforge_base/gforge/www/snippet/delete.php
   trunk/gforge_base/gforge/www/snippet/detail.php
   trunk/gforge_base/gforge/www/snippet/download.php
   trunk/gforge_base/gforge/www/snippet/index.php
   trunk/gforge_base/gforge/www/snippet/package.php
   trunk/gforge_base/gforge/www/snippet/snippet_utils.php
   trunk/gforge_base/gforge/www/snippet/submit.php
   trunk/gforge_base/gforge/www/soap/
   trunk/gforge_base/gforge/www/soap/common/
   trunk/gforge_base/gforge/www/soap/common/group.php
   trunk/gforge_base/gforge/www/soap/common/user.php
   trunk/gforge_base/gforge/www/soap/docman/
   trunk/gforge_base/gforge/www/soap/docman/docman.php
   trunk/gforge_base/gforge/www/soap/frs/
   trunk/gforge_base/gforge/www/soap/frs/frs.php
   trunk/gforge_base/gforge/www/soap/index.php
   trunk/gforge_base/gforge/www/soap/nusoap.php
   trunk/gforge_base/gforge/www/soap/pm/
   trunk/gforge_base/gforge/www/soap/pm/pm.php
   trunk/gforge_base/gforge/www/soap/reporting/
   trunk/gforge_base/gforge/www/soap/reporting/timeentry.php
   trunk/gforge_base/gforge/www/soap/scm/
   trunk/gforge_base/gforge/www/soap/scm/scm.php
   trunk/gforge_base/gforge/www/soap/tracker/
   trunk/gforge_base/gforge/www/soap/tracker/query.php
   trunk/gforge_base/gforge/www/soap/tracker/tracker.php
   trunk/gforge_base/gforge/www/softwaremap/
   trunk/gforge_base/gforge/www/softwaremap/index.php
   trunk/gforge_base/gforge/www/softwaremap/trove_list.php
   trunk/gforge_base/gforge/www/source.php
   trunk/gforge_base/gforge/www/squal/
   trunk/gforge_base/gforge/www/squal/get_session_hash.php
   trunk/gforge_base/gforge/www/stats/
   trunk/gforge_base/gforge/www/stats/graphs.php
   trunk/gforge_base/gforge/www/stats/i18n.php
   trunk/gforge_base/gforge/www/stats/index.php
   trunk/gforge_base/gforge/www/stats/lastlogins.php
   trunk/gforge_base/gforge/www/stats/projects.php
   trunk/gforge_base/gforge/www/stats/site_stats_utils.php
   trunk/gforge_base/gforge/www/stats/users_graph.php
   trunk/gforge_base/gforge/www/stats/views_graph.php
   trunk/gforge_base/gforge/www/tarballs.php
   trunk/gforge_base/gforge/www/themes/
   trunk/gforge_base/gforge/www/themes/evolvis/
   trunk/gforge_base/gforge/www/themes/evolvis/README
   trunk/gforge_base/gforge/www/themes/evolvis/Theme.class
   trunk/gforge_base/gforge/www/themes/evolvis/images/
   trunk/gforge_base/gforge/www/themes/evolvis/images/box-grad.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/box-topleft.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/box-topright.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/clear.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/adddoc12.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/caret.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/cfolder15.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/check.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/cvs16b.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/docman16b.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/forum20g.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/forum20w.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/ftp16b.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/halfcheck.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/home16b.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/index.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/mail16b.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/mail16d.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/mail16w.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/manual16c.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/msg.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/ofolder15.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/pencil.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/rss.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/save.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/survey16b.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/taskman20g.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/taskman20w.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/tracker20g.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/tracker20w.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/trash-x.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/trash.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/write16w.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/ic/xmail16w.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/logo.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/t.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/t2.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-bottomtab-end-notselected.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-bottomtab-end-selected.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-bottomtab-notselected-bg.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-bottomtab-notselected-end.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-bottomtab-notselected-notselected.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-bottomtab-notselected-selected.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-bottomtab-selected-bg.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-bottomtab-selected-end.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-bottomtab-selected-notselected.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-top-blue.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-toptab-end-notselected.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-toptab-end-selected.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-toptab-notselected-bg.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-toptab-notselected-end.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-toptab-notselected-notselected.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-toptab-notselected-selected.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-toptab-selected-bg.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-toptab-selected-end.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/theme-toptab-selected-notselected.png
   trunk/gforge_base/gforge/www/themes/evolvis/images/vert-grad.png
   trunk/gforge_base/gforge/www/themes/gforge/
   trunk/gforge_base/gforge/www/themes/gforge/Theme.class
   trunk/gforge_base/gforge/www/themes/gforge/images/
   trunk/gforge_base/gforge/www/themes/gforge/images/box-grad.png
   trunk/gforge_base/gforge/www/themes/gforge/images/box-topleft.png
   trunk/gforge_base/gforge/www/themes/gforge/images/box-topright.png
   trunk/gforge_base/gforge/www/themes/gforge/images/clear.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/adddoc12.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/caret.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/cfolder15.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/check.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/cvs16b.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/docman16b.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/forum20g.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/forum20w.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/ftp16b.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/halfcheck.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/home16b.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/index.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/mail16b.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/mail16d.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/mail16w.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/manual16c.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/msg.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/ofolder15.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/pencil.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/save.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/survey16b.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/taskman20g.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/taskman20w.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/tracker20g.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/tracker20w.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/trash-x.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/trash.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/write16w.png
   trunk/gforge_base/gforge/www/themes/gforge/images/ic/xmail16w.png
   trunk/gforge_base/gforge/www/themes/gforge/images/logo.png
   trunk/gforge_base/gforge/www/themes/gforge/images/t.png
   trunk/gforge_base/gforge/www/themes/gforge/images/t2.png
   trunk/gforge_base/gforge/www/themes/gforge/images/tabs/
   trunk/gforge_base/gforge/www/themes/gforge/images/tabs/bottomleft-inner.png
   trunk/gforge_base/gforge/www/themes/gforge/images/tabs/bottomleft.png
   trunk/gforge_base/gforge/www/themes/gforge/images/tabs/bottomright-inner.png
   trunk/gforge_base/gforge/www/themes/gforge/images/tabs/bottomright.png
   trunk/gforge_base/gforge/www/themes/gforge/images/tabs/topleft-dark.png
   trunk/gforge_base/gforge/www/themes/gforge/images/tabs/topleft-inner-dark.png
   trunk/gforge_base/gforge/www/themes/gforge/images/tabs/topleft-inner.png
   trunk/gforge_base/gforge/www/themes/gforge/images/tabs/topleft.png
   trunk/gforge_base/gforge/www/themes/gforge/images/tabs/topright-dark.png
   trunk/gforge_base/gforge/www/themes/gforge/images/tabs/topright-inner-dark.png
   trunk/gforge_base/gforge/www/themes/gforge/images/tabs/topright-inner.png
   trunk/gforge_base/gforge/www/themes/gforge/images/tabs/topright.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-bottomtab-end-notselected.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-bottomtab-end-selected.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-bottomtab-notselected-bg.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-bottomtab-notselected-end.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-bottomtab-notselected-notselected.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-bottomtab-notselected-selected.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-bottomtab-selected-bg.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-bottomtab-selected-end.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-bottomtab-selected-notselected.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-top-blue.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-toptab-end-notselected.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-toptab-end-selected.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-toptab-notselected-bg.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-toptab-notselected-end.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-toptab-notselected-notselected.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-toptab-notselected-selected.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-toptab-selected-bg.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-toptab-selected-end.png
   trunk/gforge_base/gforge/www/themes/gforge/images/theme-toptab-selected-notselected.png
   trunk/gforge_base/gforge/www/themes/gforge/images/vert-grad.png
   trunk/gforge_base/gforge/www/themes/index.php
   trunk/gforge_base/gforge/www/themes/osx/
   trunk/gforge_base/gforge/www/themes/osx/README
   trunk/gforge_base/gforge/www/themes/osx/Theme.class
   trunk/gforge_base/gforge/www/themes/osx/images/
   trunk/gforge_base/gforge/www/themes/osx/images/background.png
   trunk/gforge_base/gforge/www/themes/osx/images/clear.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/
   trunk/gforge_base/gforge/www/themes/osx/images/ic/adddoc12.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/caret.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/cfolder15.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/check.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/cvs16b.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/docman16b.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/forum20g.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/forum20w.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/ftp16b.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/halfcheck.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/home16b.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/index.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/mail16b.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/mail16d.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/mail16w.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/manual16c.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/msg.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/ofolder15.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/pencil.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/save.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/survey16b.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/taskman20g.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/taskman20w.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/tracker20g.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/tracker20w.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/trash-x.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/trash.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/write16w.png
   trunk/gforge_base/gforge/www/themes/osx/images/ic/xmail16w.png
   trunk/gforge_base/gforge/www/themes/osx/images/logo.png
   trunk/gforge_base/gforge/www/themes/osx/images/logohover.png
   trunk/gforge_base/gforge/www/themes/osx/images/point1.png
   trunk/gforge_base/gforge/www/themes/osx/images/proj/
   trunk/gforge_base/gforge/www/themes/osx/images/proj/cvs16b.png
   trunk/gforge_base/gforge/www/themes/osx/images/proj/docman16b.png
   trunk/gforge_base/gforge/www/themes/osx/images/proj/ftp16b.png
   trunk/gforge_base/gforge/www/themes/osx/images/proj/home16b.png
   trunk/gforge_base/gforge/www/themes/osx/images/proj/mail16b.png
   trunk/gforge_base/gforge/www/themes/osx/images/proj/mail16d.png
   trunk/gforge_base/gforge/www/themes/osx/images/proj/manual16c.png
   trunk/gforge_base/gforge/www/themes/osx/images/proj/notes16.png
   trunk/gforge_base/gforge/www/themes/osx/images/proj/survey16b.png
   trunk/gforge_base/gforge/www/themes/osx/images/proj/taskman16b.png
   trunk/gforge_base/gforge/www/themes/osx/images/rateit.png
   trunk/gforge_base/gforge/www/themes/osx/images/tabs/
   trunk/gforge_base/gforge/www/themes/osx/images/tabs/deselect.png
   trunk/gforge_base/gforge/www/themes/osx/images/tabs/leftblenddeselect.png
   trunk/gforge_base/gforge/www/themes/osx/images/tabs/leftblendselect.png
   trunk/gforge_base/gforge/www/themes/osx/images/tabs/leftdeselect.png
   trunk/gforge_base/gforge/www/themes/osx/images/tabs/leftselect.png
   trunk/gforge_base/gforge/www/themes/osx/images/tabs/rightblenddeselect.png
   trunk/gforge_base/gforge/www/themes/osx/images/tabs/rightblendselect.png
   trunk/gforge_base/gforge/www/themes/osx/images/tabs/rightdeselect.png
   trunk/gforge_base/gforge/www/themes/osx/images/tabs/rightselect.png
   trunk/gforge_base/gforge/www/themes/osx/images/tabs/ruledeselect.png
   trunk/gforge_base/gforge/www/themes/osx/images/tabs/ruleselect.png
   trunk/gforge_base/gforge/www/themes/osx/images/tabs/select.png
   trunk/gforge_base/gforge/www/themes/ultralite/
   trunk/gforge_base/gforge/www/themes/ultralite/Theme.class
   trunk/gforge_base/gforge/www/top/
   trunk/gforge_base/gforge/www/top/index.php
   trunk/gforge_base/gforge/www/top/mostactive.php
   trunk/gforge_base/gforge/www/top/toplist.php
   trunk/gforge_base/gforge/www/tracker/
   trunk/gforge_base/gforge/www/tracker/add.php
   trunk/gforge_base/gforge/www/tracker/admin/
   trunk/gforge_base/gforge/www/tracker/admin/form-addcanned.php
   trunk/gforge_base/gforge/www/tracker/admin/form-addextrafield.php
   trunk/gforge_base/gforge/www/tracker/admin/form-addextrafieldoption.php
   trunk/gforge_base/gforge/www/tracker/admin/form-clonetracker.php
   trunk/gforge_base/gforge/www/tracker/admin/form-deleteextrafield.php
   trunk/gforge_base/gforge/www/tracker/admin/form-deletetracker.php
   trunk/gforge_base/gforge/www/tracker/admin/form-extrafieldcopy.php
   trunk/gforge_base/gforge/www/tracker/admin/form-updatecanned.php
   trunk/gforge_base/gforge/www/tracker/admin/form-updateextrafield.php
   trunk/gforge_base/gforge/www/tracker/admin/form-updateextrafieldelement.php
   trunk/gforge_base/gforge/www/tracker/admin/form-updatetracker.php
   trunk/gforge_base/gforge/www/tracker/admin/form-uploadtemplate.php
   trunk/gforge_base/gforge/www/tracker/admin/ind.php
   trunk/gforge_base/gforge/www/tracker/admin/index.php
   trunk/gforge_base/gforge/www/tracker/admin/tracker.php
   trunk/gforge_base/gforge/www/tracker/admin/updates.php
   trunk/gforge_base/gforge/www/tracker/browse.php
   trunk/gforge_base/gforge/www/tracker/deleteartifact.php
   trunk/gforge_base/gforge/www/tracker/detail.php
   trunk/gforge_base/gforge/www/tracker/download.php
   trunk/gforge_base/gforge/www/tracker/downloadcsv.php
   trunk/gforge_base/gforge/www/tracker/include/
   trunk/gforge_base/gforge/www/tracker/include/ArtifactFileHtml.class
   trunk/gforge_base/gforge/www/tracker/include/ArtifactHtml.class
   trunk/gforge_base/gforge/www/tracker/include/ArtifactTypeHtml.class
   trunk/gforge_base/gforge/www/tracker/ind.php
   trunk/gforge_base/gforge/www/tracker/index.php
   trunk/gforge_base/gforge/www/tracker/mod-limited.php
   trunk/gforge_base/gforge/www/tracker/mod.php
   trunk/gforge_base/gforge/www/tracker/opener_tasks.js
   trunk/gforge_base/gforge/www/tracker/query.php
   trunk/gforge_base/gforge/www/tracker/reporting/
   trunk/gforge_base/gforge/www/tracker/reporting/index.php
   trunk/gforge_base/gforge/www/tracker/reporting/trackeract_graph.php
   trunk/gforge_base/gforge/www/tracker/reporting/trackerpie_graph.php
   trunk/gforge_base/gforge/www/tracker/taskmgr.php
   trunk/gforge_base/gforge/www/tracker/tracker.php
   trunk/gforge_base/gforge/www/users
Log:
Evolvis GForge Files added

Added: trunk/gforge_base/gforge/backend/DatabaseDump.pl
===================================================================
--- trunk/gforge_base/gforge/backend/DatabaseDump.pl	                        (rev 0)
+++ trunk/gforge_base/gforge/backend/DatabaseDump.pl	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,293 @@
+#!/usr/bin/perl -w
+#
+# SourceForge: Breaking Down the Barriers to Open Source Development
+# Copyright 1999-2000 (c) The SourceForge Crew
+# http://sourceforge.net
+#
+# $Id: DatabaseDump.pl 2683 2004-02-18 09:18:27Z gsmet $
+#
+use DBI;
+use Sys::Hostname;
+use POSIX qw(strftime);
+
+&open_log_file;
+
+# All of the files that we will be creating
+my @file_array = ("user_dump", "group_dump", "ssh_dump", "list_dump", "alias_dump", "httpd_conf", "aliases", "dns_sourceforge_net");
+
+# Check to make sure that the environment is clean
+if (! -d $file_dir) {
+	&logme("The file directory doesn't exist: $file_dir");
+	exit 1;
+}
+
+foreach(@file_array) {
+	if (-f $file_dir.$_) {
+		&logme("Another Dump File Exists (Overwriting): $_");
+	}
+}
+
+my ($foo, $bar, $x);
+	
+# open up database include file and get the database variables
+open(FILE, $db_include) || die "Can't open $db_include: $!\n";
+while ($x = <FILE>) {
+	($foo, $bar) = split /=/, $x;
+	if ($foo) { eval $x; }
+}
+close(FILE);
+
+# connect to the database
+$dbh ||= DBI->connect("DBI:mysql:$sys_dbname:$sys_dbhost", "$sys_dbuser", "$sys_dbpasswd");
+
+
+print("Dumping Table Data\n");
+
+# Okay lets dump and configure all the tables now.
+
+my ($query, $c, @tmp_array);
+
+###################################
+# First the users Information.
+###################################
+print("Dumping users Data: ");
+
+$query = "select unix_uid, unix_status, user_name, shell, unix_pw, realname from users where unix_status != \"N\"";
+$c = $dbh->prepare($query);
+$c->execute();
+
+while(my ($id, $status, $username, $shell, $passwd, $realname) = $c->fetchrow()) {
+	$username =~ s/://g;
+	push @tmp_array, "$id:$status:$username:$shell:$passwd:$realname\n";
+}
+
+&done("user_dump", @tmp_array);
+undef @tmp_array;
+
+
+###################################
+# Now Dump the Group Information.
+###################################
+print("Dumping Group Data: ");
+
+$query = "select group_id,unix_group_name,status from groups";
+$c = $dbh->prepare($query);
+$c->execute();
+
+while(my ($group_id, $group_name, $status) = $c->fetchrow()) {
+	$new_query = "select users.user_name AS user_name FROM users,user_group WHERE users.user_id=user_group.user_id AND group_id=$group_id";
+	$d = $dbh->prepare($new_query);
+	$d->execute();
+
+	$user_list = "";
+	while($user_name = $d->fetchrow()) {
+		$user_list .= "$user_name,";
+	}
+
+	$group_list = "$group_name:$status:$group_id:$user_list\n";
+	$group_list =~ s/,$//;	# regex out the last comma on the line
+
+	push @tmp_array, $group_list;
+}
+
+&done("group_dump", @tmp_array);
+undef @tmp_array;
+
+###################################
+# Dump the SSH authorized_keys Data
+###################################
+print("Dumping SSH Data: ");
+
+$query = "SELECT user_name,authorized_keys FROM users WHERE authorized_keys != \"\"";
+$c = $dbh->prepare($query);
+$c->execute();
+
+while(my ($username, $ssh_key) = $c->fetchrow()) {
+	$ssh_key =~ s/://g;
+	push @tmp_array, "$username:$ssh_key\n";
+}
+
+# Now write out the files
+&done("ssh_dump", @tmp_array);
+undef @tmp_array;
+
+
+###################################
+# Dump the Mailing list Information
+###################################
+print("Dumping Mailing List Data: ");
+
+$query = "SELECT users.user_name,mail_group_list.list_name,mail_group_list.password,mail_group_list.status FROM mail_group_list,users WHERE mail_group_list.list_admin=users.user_id";
+$c = $dbh->prepare($query);
+$c->execute();
+
+while(my ($list_name, $list_admin, $password, $status) = $c->fetchrow()) {
+	push @tmp_array, "$list_name:$list_admin:$password:$status\n";
+}
+
+&done("list_dump", @tmp_array);
+undef @tmp_array;
+
+###################################
+# Apache Dump and configuration
+###################################
+print("Dumping httpd.conf Data: ");
+$query = "SELECT http_domain,unix_group_name,group_name,status FROM groups WHERE unix_box='shell1' AND http_domain LIKE '%.%'";
+$c = $dbh->prepare($query);
+$c->execute();
+
+ at tmp_array = open_array_file("./zones/httpd.conf.zone");
+
+while(my ($http_domain,$unix_group_name,$group_name,$status) = $c->fetchrow()) {
+	if ($status eq "A") {
+		push @tmp_array, "\n\n### Host entries for: $group_name\n\n";
+		push @tmp_array, "<Directory \"$grpdir_prefix$unix_group_name/htdocs\">\n";
+		push @tmp_array, "    AllowOverride AuthConfig FileInfo\n";
+		push @tmp_array, "    Options Indexes Includes\n";
+		push @tmp_array, "    Order allow,deny\n";
+		push @tmp_array, "    Allow from all\n";
+		push @tmp_array, "</Directory>\n";
+		push @tmp_array, "<Directory \"$grpdir_prefix$unix_group_name/cgi-bin\">\n";
+		push @tmp_array, "    AllowOverride AuthConfig FileInfo\n";
+		push @tmp_array, "    Options ExecCGI\n";
+		push @tmp_array, "    Order allow,deny\n";
+		push @tmp_array, "    Allow from all\n";
+		push @tmp_array, "</Directory>\n";
+		push @tmp_array, "<VirtualHost 192.168.4.52>\n";
+		push @tmp_array, "    DocumentRoot \"$grpdir_prefix$unix_group_name/htdocs/\"\n";
+		push @tmp_array, "    CustomLog $grpdir_prefix$unix_group_name/log/combined_log combined\n";
+		push @tmp_array, "    ScriptAlias /cgi-bin/ \"$grpdir_prefix$unix_group_name/cgi-bin/\"\n";
+		push @tmp_array, "    Servername $http_domain\n";
+		push @tmp_array, "</VirtualHost>\n";
+	}
+}
+
+&done("httpd_conf", @tmp_array);
+undef @tmp_array;
+
+###################################
+# Dump Project Mail Aliases (virtusertable)
+###################################
+print("Dumping Project Mail Alias Data: ");
+$query = "SELECT mailaliases.user_name,groups.http_domain,mailaliases.email_forward FROM mailaliases,groups WHERE mailaliases.group_id=groups.group_id";
+
+$c = $dbh->prepare($query);
+$c->execute();
+
+while(($username,$domainname,$userlist) = $c->fetchrow()) {
+        push @tmp_array, "$username:$domainname:$userlist\n";
+}
+
+# now dump all the normal users stuff
+$query = "SELECT user_name,email FROM users WHERE status = \"A\" AND email != \"\"";
+$c = $dbh->prepare($query);
+$c->execute();
+while(my ($username, $email) = $c->fetchrow()) {
+	push @tmp_array, "$username:users.sourceforge.net:$username\n";
+}
+
+&done("alias_dump", @tmp_array);
+undef @tmp_array;
+
+###################################
+# Dump User Mail Aliases (/etc/aliases)
+###################################
+print("Dumping /etc/aliases Data: ");
+
+ at tmp_array = open_array_file("./zones/aliases.zone");
+
+# First lets Dump the Mailing List Info
+push @tmp_array, "\n\n### Begin Mailing List Aliases ###\n\n";
+
+$query = "SELECT list_name from mail_group_list";
+$c = $dbh->prepare($query);
+$c->execute();
+while(my ($list_name) = $c->fetchrow()) {
+	push @tmp_array, sprintf("%-60s%-10s","$list_name\@lists.sourceforge.net:", "\"|/usr/local/mailman/mail/wrapper post $list_name\"\n");
+	push @tmp_array, sprintf("%-60s%-10s","$list_name-admin\@lists.sourceforge.net:", "\"|/usr/local/mailman/mail/wrapper mailowner $list_name\"\n");
+	push @tmp_array, sprintf("%-60s%-10s","$list_name-request\@lists.sourceforge.net:", "\"|/usr/local/mailman/mail/wrapper mailcmd $list_name\"\n");
+}
+
+&done("aliases", @tmp_array);
+undef @tmp_array;
+
+###################################
+# Dump DNS Information
+###################################
+print("Dumping DNS Zone File Data: ");
+
+ at tmp_array = open_array_file("./zones/dns.zone");
+
+# Update the Serial Number
+$date_line = $tmp_array[1];
+$date_line =~ s/\t\t\t/\t/;
+
+my ($blah,$date_str,$comments) = split("	", $date_line);
+$date = $date_str;
+
+my $serial = substr($date, 8, 2);
+my $old_day = substr($date, 6, 2);
+
+$serial++;
+
+$now_string = strftime "%Y%m%d", localtime;
+$new_day = substr($now_string, 6, 1);
+
+if ($old_day != $new_day) { $serial = "01"; }
+
+$new_serial = $now_string.$serial;
+
+$tmp_array[1] = "		$blah	$new_serial	$comments";
+
+&write_array_file("./zones/dns.zone", @tmp_array); # write the new serial out the zone file
+
+$query = "SELECT http_domain,unix_group_name,group_name,unix_box FROM groups WHERE http_domain LIKE '%.%' AND status = 'A'";
+$c = $dbh->prepare($query);
+$c->execute();
+
+while(my ($http_domain,$unix_group_name,$group_name,$unix_box) = $c->fetchrow()) {
+	($foo, $foo, $foo, $foo, @addrs) = gethostbyname("$unix_box.sourceforge.net");
+	@blah = unpack('C4', $addrs[0]);
+	$ip = join(".", @blah);
+
+	push @tmp_array, sprintf("%-24s%-16s",$unix_group_name,"IN\tA\t" . "$ip\n");
+	push @tmp_array, sprintf("%-24s%-28s","", "IN\tMX\t" . "mail1.sourceforge.net.\n");
+	push @tmp_array, sprintf("%-24s%-30s","cvs.".$unix_group_name,"IN\tCNAME\t" . "cvs1.sourceforge.net."."\n\n");
+}
+
+&done("dns_sourceforge_net", @tmp_array);
+undef @tmp_array;
+
+$dbh->disconnect();
+
+sub done {	# Done Function for db_parse.pl
+	my ($file_name, @file_array) = @_;
+	
+	write_array_file($file_dir.$file_name, @file_array);
+	print("Done.\n");
+}
+
+sub open_array_file {	# File open function, spews the entire file to an array.
+        my $filename = shift(@_);
+
+	# Now read in the file as a big array
+        open (FD, $filename) || die "Can't open $filename: $!.\n";
+        @tmp_array = <FD>;
+        close(FD);
+        
+        return @tmp_array;
+}       
+
+
+sub write_array_file {	# File write function.
+        my ($file_name, @file_array) = @_;
+
+	# Write this array out to $filename
+        open(FD, ">$file_name") || die "Can't open $file_name: $!.\n";
+        foreach (@file_array) { 
+                if ($_ ne '') { 
+                        print FD;
+                }       
+        }       
+        close(FD);
+}      

Added: trunk/gforge_base/gforge/backend/include.pl
===================================================================
--- trunk/gforge_base/gforge/backend/include.pl	                        (rev 0)
+++ trunk/gforge_base/gforge/backend/include.pl	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,83 @@
+#!/usr/bin/perl -w
+#
+# SourceForge: Breaking Down the Barriers to Open Source Development
+# Copyright 1999-2000 (c) The SourceForge Crew
+# http://sourceforge.net
+#
+# $Id: include.pl 2683 2004-02-18 09:18:27Z gsmet $
+#
+
+########################
+# global configuration #
+########################
+$config{'database_include'} 	= '/etc/gforge/database.inc';		# database include file
+$config{'lock_file'}		= '/tmp/sf-backend';		# lockfile location
+$config{'log_file'}		= '/home/dummy/backend.log';	# logfile location
+$config{'group_dir_prefix'} 	= '/home/groups';		# prefix for group directories
+$config{'user_dir_prefix'} 	= '/home/users';		# prefix for user directories
+$config{'database_dump_dir'}	= '/home/dummy/dumps';		# where are the database dumps kept
+$config{'delete_tar_dir'}	= '/tmp';			# place to stick tarballs of deleted accounts/groups
+$config{'dummy_uid'}		= getpwnam('dummy');		# userid of the dummy user
+$config{'days_since_epoch'} 	= int(time()/3600/24);		# number of days since the epoch
+$config{'hostname'}		= hostname();			# machine hostname
+
+####################
+# open the logfile #
+####################
+sub open_log_file {
+	open(Log, ">>$config{'log_file'}") || die "Couldn't Open Logfile: $!\n";
+	select(Log);
+	$| = 1;
+	return;
+}
+
+##############################
+# log message to the logfile #
+##############################
+sub logme {
+	my $msg = shift(@_);
+	my $time = strftime "%Y-%m-%d - %T", localtime;
+	print "$time\t$msg\n";
+	return;
+}
+
+##########################
+# exit the script nicely #
+##########################
+sub exit_nicely {
+	&logme("------ Script Ended -------\n");
+	close(Log);
+	exit 0;
+}
+
+#########################################
+# open a file and read it into an array #
+#########################################
+sub open_array {
+	my $filename = shift(@_);
+
+	# Now read in the file as a big array
+	open (FD, $filename) || die &logme("Can't open $filename: $!");
+	@tmp_array = <FD>;
+        close(FD);
+
+	&logme("Opened $filename with $@tmp_array Lines");
+        return @tmp_array;
+}               
+
+################################
+# write an array out to a file #
+################################
+sub write_array {
+	my ($filename, @filearray) = @_;
+
+	# Write this array out to $filename
+	open(FD, ">$filename") || die &logme("Can't open $filename: $!");
+	foreach (@filearray) {
+		if ($_ ne '') {
+			print FD;
+		}
+	}
+	&logme("Wrote $filename with $#filearray Lines");
+	close(FD);
+}


Property changes on: trunk/gforge_base/gforge/backend/include.pl
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/gforge_base/gforge/backend/shell/apache.sh
===================================================================
(Binary files differ)


Property changes on: trunk/gforge_base/gforge/backend/shell/apache.sh
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:mime-type
   + application/octet-stream

Added: trunk/gforge_base/gforge/backend/zones/aliases.zone
===================================================================
--- trunk/gforge_base/gforge/backend/zones/aliases.zone	                        (rev 0)
+++ trunk/gforge_base/gforge/backend/zones/aliases.zone	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,40 @@
+# This file is autogenerated
+# Updating it here will do no good, it will just get
+# overwritten
+#
+
+# Basic system aliases -- these MUST be present.
+MAILER-DAEMON:  postmaster
+postmaster:     root
+
+# General redirections for pseudo accounts.
+bin:            root
+daemon:         root
+games:          root
+ingres:         root
+nobody:         root
+system:         root
+toor:           root
+uucp:           root
+
+# Well-known aliases.
+manager:        root
+dumper:         root
+operator:       root
+
+# trap decode to catch security attacks
+decode:         root
+
+admin at sourceforge.net:   fusion94,dtype,bigdisk,precision
+nfs-admin:	hjl at users.sourceforge.net, dhiggen at users.sourceforge.net
+mailman-owner at lists.sourceforge.net: dtype
+
+fusion94 at sourceforge.net: fusion94 at valinux.com
+dtype at sourceforge.net: ds at valinux.com
+bigdisk at sourceforge.net: tperdue at valinux.com
+precision at sourceforge.net: precision at valinux.com
+
+#
+# From Here Out is autogenerated
+#
+

Added: trunk/gforge_base/gforge/backend/zones/dns.zone
===================================================================
--- trunk/gforge_base/gforge/backend/zones/dns.zone	                        (rev 0)
+++ trunk/gforge_base/gforge/backend/zones/dns.zone	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,70 @@
+@    IN    SOA    ns1.varesearch.com. hostmaster.sourceforge.net. (
+			2000010701	; serial number, in date form
+			10800		; refresh 4 minutes
+			3600		; retry interval 2 minutes
+			604800		; expire
+			3600		; default ttl
+)
+
+;
+; Name Servers
+;
+                	IN      NS      ns1.varesearch.com.
+			IN      NS      ns2.varesearch.com.
+			IN	NS	ns1.sourceforge.net.
+
+mailhost		IN	A	127.0.0.1
+localhost		IN	A	127.0.0.1
+
+
+sourceforge.net.	IN      A       209.81.8.17
+	                IN      MX      10 mail1.sourceforge.net.
+			IN	MX	15 mail.urw.org.
+;
+; Host Names
+;
+
+beta			IN	CNAME	sourceforge.net.	
+www             	IN      CNAME   sourceforge.net.
+mail1            	IN      A	209.81.8.35
+users			IN	CNAME	sourceforge.net.
+images			IN	A	209.81.8.36
+blink182		IN	A	209.81.8.38
+webdev			IN	A	209.81.8.39
+download		IN	A	209.81.8.41
+
+shell1			IN	A	209.81.8.42
+			IN	MX	10 mail1.sourceforge.net.
+
+shell			IN	CNAME	shell1.sourceforge.net.
+cvs1	           	IN      A	209.81.8.43
+cvs			IN	CNAME	cvs1.sourceforge.net.
+mail1			IN	A	209.81.8.35
+			IN	MX	10 mail1.sourceforge.net.
+lists			IN	A	209.81.8.35
+			IN	MX	10 lists.sourceforge.net.
+ns1			IN	CNAME	mail1.sourceforge.net.
+
+;
+; Sourceforge Internal Servers (192.168.1.x)
+;
+backup.i		IN	A	192.168.1.12
+geocrawler.i		IN	A	192.168.1.45
+remission.i		IN	A	192.168.1.47
+underworld.i		IN	A	192.168.1.53
+web1.i			IN	A	192.168.1.100
+web2.i			IN	A	192.168.1.101
+fallover.i		IN	A	192.168.1.200
+police.i		IN	A	192.168.1.254
+
+;
+; Projects Internal Servers (192.168.4.x)
+moby.i			IN	A	192.168.4.11
+backup.proj.i		IN	A	192.168.4.12
+cvs1.i			IN	A	192.168.4.46
+shell1.i		IN	A	192.168.4.52
+police.proj.i		IN	A	192.168.4.254
+
+;
+; *** From here out is auto-generated ***
+;

Added: trunk/gforge_base/gforge/backend/zones/sendmail.cw.zone
===================================================================
--- trunk/gforge_base/gforge/backend/zones/sendmail.cw.zone	                        (rev 0)
+++ trunk/gforge_base/gforge/backend/zones/sendmail.cw.zone	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,12 @@
+# sendmail.cw - include all aliases for your machine here.
+sourceforge.net
+sourceforge.org
+shell1.sourceforge.net
+
+vaspecialprojects.com
+vaspecialprojects.org
+vaspecialprojects.net
+
+linuxnfs.sourceforge.org
+
+# Everything from here out is autogenerated

Added: trunk/gforge_base/gforge/common/docman/Document.class
===================================================================
--- trunk/gforge_base/gforge/common/docman/Document.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/docman/Document.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,484 @@
+<?php
+/**
+ * GForge Doc Mgr Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: Document.class 5356 2006-03-08 14:20:11Z tperdue $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ * 
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+/*
+	Document Manager
+
+	by Quentin Cregan, SourceForge 06/2000
+
+	Complete OO rewrite by Tim Perdue 1/2003
+*/
+
+require_once('common/include/Error.class');
+
+class Document extends Error {
+
+	/**
+	 * Associative array of data from db.
+	 *
+	 * @var	 array   $data_array.
+	 */
+	var $data_array;
+
+	/**
+	 * The Group object.
+	 *
+	 * @var	 object  $Group.
+	 */
+	var $Group; //group object
+
+	/**
+	 *  Constructor.
+	 *
+	 *	@param	object	The Group object to which this document is associated.
+	 *  @param  int	 The docid.
+	 *  @param  array	The associative array of data.
+	 *	@return	boolean	success.
+	 */
+	function Document(&$Group, $docid=false, $arr=false) {
+		$this->Error();
+		if (!$Group || !is_object($Group)) {
+			$this->setNotValidGroupObjectError();
+			return false;
+		}
+		if ($Group->isError()) {
+			$this->setError('Document:: '.$Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $Group;
+
+		if ($docid) {
+			if (!$arr || !is_array($arr)) {
+				if (!$this->fetchData($docid)) {
+					return false;
+				}
+			} else {
+				$this->data_array =& $arr;
+				if ($this->data_array['group_id'] != $this->Group->getID()) {
+					$this->setError('Group_id in db result does not match Group Object');
+					$this->data_array = null;
+					return false;
+				}
+			}
+			if (!$this->isPublic()) {
+				$perm =& $this->Group->getPermission( session_get_user() );
+
+				if (!$perm || !is_object($perm) || !$perm->isMember()) {
+					$this->setPermissionDeniedError();
+					$this->data_array = null;
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+
+	/**
+	 *	create - use this function to create a new entry in the database.
+	 *
+	 *	@param	string	The filename of this document. Can be a URL.
+	 *	@param	string	The filetype of this document. If filename is URL, this should be 'URL';
+	 *	@param	string	The contents of this document (should be addslashes()'d before entry).
+	 *	@param	int	The doc_group id of the doc_groups table.
+	 *	@param	string	The title of this document.
+	 *	@param	int	The language id of the supported_languages table.
+	 *	@param	string	The description of this document.
+	 *	@return	boolean	success.
+	 */
+	function create($filename,$filetype,$data,$doc_group,$title,$language_id,$description) {
+
+		global $Language;
+		if (strlen($title) < 5) {
+			$this->setError($Language->getText('docman_common','error_min_title_length'));
+			return false;
+		}
+		if (strlen($description) < 10) {
+			$this->setError($Language->getText('docman_common','error_min_desc_length'));
+			return false;
+		}
+
+/*
+		$perm =& $this->Group->getPermission( session_get_user() );
+		if (!$perm || !is_object($perm) || !$perm->isDocEditor()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+*/
+		$user_id = ((session_loggedin()) ? user_getid() : 100);
+
+		$doc_initstatus = '3';
+		// If Editor - uploaded Documents are ACTIVE
+		if ( session_loggedin() ) {
+			$perm =& $this->Group->getPermission( session_get_user() );
+			if ($perm && is_object($perm) && $perm->isDocEditor()) {
+				$doc_initstatus = '1';
+			}
+		}
+
+		// If $filetype is "text/plain", $body convert UTF-8 encoding.
+		if (strcasecmp($filetype,"text/plain") === 0 &&
+			function_exists('mb_convert_encoding') &&
+			function_exists('mb_detect_encoding')) {
+			$data = mb_convert_encoding($data,'UTF-8',mb_detect_encoding($data));
+		}
+		
+		$filesize = strlen($data);
+
+		$sql="INSERT INTO doc_data (group_id,title,description,createdate,doc_group,
+			stateid,language_id,filename,filetype,filesize,data,created_by)
+			VALUES ('".$this->Group->getId()."',
+			'". htmlspecialchars($title) ."',
+			'". htmlspecialchars($description) ."',
+			'". time() ."',
+			'$doc_group',
+			'$doc_initstatus',
+			'$language_id',
+			'$filename',
+			'$filetype',
+			'$filesize',
+			'". base64_encode(stripslashes($data)) ."',
+			'$user_id')";
+
+		db_begin();
+		$result=db_query($sql);
+		if (!$result) {
+			$this->setError('Error Adding Document: '.db_error());
+			db_rollback();
+			return false;
+		}
+		$docid=db_insertid($result,'doc_data','docid');
+		if (!$this->fetchData($docid)) {
+			db_rollback();
+			return false;
+		}
+		$this->sendNotice(true);
+		db_commit();
+		return true;
+	}
+
+	/**
+	 *  fetchData() - re-fetch the data for this document from the database.
+	 *
+	 *  @param  int	 The document id.
+	 *	@return	boolean	success
+	 */
+	function fetchData($docid) {
+		global $Language;
+		$res=db_query("SELECT * FROM docdata_vw
+			WHERE docid='$docid'
+			AND group_id='". $this->Group->getID() ."'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError($Language->getText('docman_common_doc','invalid_docid'));
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	getGroup - get the Group object this Document is associated with.
+	 *
+	 *	@return	Object	The Group object.
+	 */
+	function &getGroup() {
+		return $this->Group;
+	}
+
+	/**
+	 *	getID - get this docid.
+	 *
+	 *	@return	int	The docid.
+	 */
+	function getID() {
+		return $this->data_array['docid'];
+	}
+
+	/**
+	 *	getName - get the name of this document.
+	 *
+	 *	@return string	The name of this document.
+	 */
+	function getName() {
+		return $this->data_array['title'];
+	}
+
+	/**
+	 *	getDescription - the description of this document.
+	 *
+	 *	@return string	The description.
+	 */
+	function getDescription() {
+		return $this->data_array['description'];
+	}
+
+	/**
+	 *	isURL - whether this document is a URL and not a local file.
+	 *
+	 *	@return	boolean	is_url.
+	 */
+	function isURL() {
+		return ($this->data_array['filetype'] == 'URL');
+	}
+
+	/**
+	 *	isPublic - whether this document is available to the general public.
+	 *
+	 *	@return	boolean	is_public.
+	 */
+	function isPublic() {
+		return (($this->data_array['stateid'] == 1) ? true  : false);
+	}
+
+	/**
+	 *	getStateID - get this stateid.
+	 *
+	 *	@return	int	The stateid.
+	 */
+	function getStateID() {
+		return $this->data_array['stateid'];
+	}
+
+	/**
+	 *	getStateName - the statename of this document.
+	 *
+	 *	@return string	The statename.
+	 */
+	function getStateName() {
+		return $this->data_array['state_name'];
+	}
+
+	/**
+	 *	getLanguageID - get this language_id.
+	 *
+	 *	@return	int	The language_id.
+	 */
+	function getLanguageID() {
+		return $this->data_array['language_id'];
+	}
+
+	/**
+	 *	getLanguageName - the language_name of this document.
+	 *
+	 *	@return string	The language_name.
+	 */
+	function getLanguageName() {
+		return $this->data_array['language_name'];
+	}
+
+	/**
+	 *	getDocGroupID - get this doc_group_id.
+	 *
+	 *	@return	int	The doc_group_id.
+	 */
+	function getDocGroupID() {
+		return $this->data_array['doc_group'];
+	}
+
+	/**
+	 *	getDocGroupName - the doc_group_name of this document.
+	 *
+	 *	@return string	The docgroupname.
+	 */
+	function getDocGroupName() {
+		return $this->data_array['group_name'];
+	}
+
+	/**
+	 *	getCreatorID - get this creator's user_id.
+	 *
+	 *	@return	int	The user_id.
+	 */
+	function getCreatorID() {
+		return $this->data_array['created_by'];
+	}
+
+	/**
+	 *	getCreatorUserName - the unix name of the person who created this document.
+	 *
+	 *	@return string	The unix name of the creator.
+	 */
+	function getCreatorUserName() {
+		return $this->data_array['user_name'];
+	}
+
+	/**
+	 *	getCreatorRealName - the real name of the person who created this document.
+	 *
+	 *	@return string	The real name of the creator.
+	 */
+	function getCreatorRealName() {
+		return $this->data_array['realname'];
+	}
+
+	/**
+	 *	getCreatorEmail - the email of the person who created this document.
+	 *
+	 *	@return string	The email of the creator.
+	 */
+	function getCreatorEmail() {
+		return $this->data_array['email'];
+	}
+
+	/**
+	 *	getFileName - the filename of this document.
+	 *
+	 *	@return string	The filename.
+	 */
+	function getFileName() {
+		return $this->data_array['filename'];
+	}
+
+	/**
+	 *	getFileType - the filetype of this document.
+	 *
+	 *	@return string	The filetype.
+	 */
+	function getFileType() {
+		return $this->data_array['filetype'];
+	}
+
+	/**
+	 *	getFileData - the filedata of this document.
+	 *
+	 *	@return string	The filedata.
+	 */
+	function getFileData() {
+		//
+		//	Because this could be a large string, we only fetch if we actually need it
+		//
+		$res=db_query("SELECT data FROM doc_data WHERE docid='".$this->getID()."'");
+		return base64_decode(db_result($res,0,'data'));
+	}
+	
+	/**
+	* getFileSize - Return the size of the document
+	*
+	* @return	int	The file size
+	*/
+	function getFileSize() {
+		return $this->data_array['filesize'];
+	}
+
+	/**
+	 *	update - use this function to update an existing entry in the database.
+	 *
+	 *	@param	string	The filename of this document. Can be a URL.
+	 *	@param	string	The filetype of this document. If filename is URL, this should be 'URL';
+	 *	@param	string	The contents of this document (should be addslashes()'d before entry).
+	 *	@param	int	The doc_group id of the doc_groups table.
+	 *	@param	string	The title of this document.
+	 *	@param	int	The language id of the supported_languages table.
+	 *	@param	string	The description of this document.
+	 *	@param	int	The state id of the doc_states table.
+	 *	@return	boolean	success.
+	 */
+	function update($filename,$filetype,$data,$doc_group,$title,$language_id,$description,$stateid) {
+		global $Language;
+		if (strlen($title) < 5) {
+			$this->setError($Language->getText('docman_common','error_min_title_length'));
+			return false;
+		}
+		if (strlen($description) < 10) {
+			$this->setError($Language->getText('docman_common','error_min_desc_length'));
+			return false;
+		}
+
+		$perm =& $this->Group->getPermission( session_get_user() );
+
+		if (!$perm || !is_object($perm) || !$perm->isDocEditor()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		if ($data) {
+			$filesize = strlen($data);
+			$datastr="data='". base64_encode(stripslashes($data)) ."', filesize='".$filesize."',";
+		}
+
+		$res=db_query("UPDATE doc_data SET
+			title='". htmlspecialchars($title) ."',
+			description='". htmlspecialchars($description) ."',
+			stateid='$stateid',
+			doc_group='$doc_group',
+			filetype='$filetype',
+			filename='$filename',
+			$datastr
+			language_id='$language_id',
+			updatedate='". time() ."'
+			WHERE group_id='".$this->Group->getID()."'
+			AND docid='".$this->getID()."'");
+
+		if (!$res || db_affected_rows($res) < 1) {
+			$this->setOnUpdateError(db_error());
+			return false;
+		}
+		$this->sendNotice(false);
+		return true;
+	}
+
+	/**
+	*   sendNotice - Notifies of document submissions
+	*/
+	function sendNotice ($new=true) {
+		$BCC = $this->Group->getDocEmailAddress();
+		if (strlen($BCC) > 0) {
+			$subject = '['.$this->Group->getPublicName().'] New document - '.$this->getName();
+			$body = "Project: ".$this->Group->getPublicName()."\n";
+			$body .= "Group: ".$groupname."\n";
+			$body .= "Document title: ".$this->getName()."\n";
+			$body .= "Document description: ".util_unconvert_htmlspecialchars( $this->getDescription() )."\n";
+			$body .= "Submitter: ".$this->getCreatorRealName()." (".$this->getCreatorUserName().") \n";
+			$body .= "\n\n-------------------------------------------------------".
+				"\nFor more info, visit:".
+				"\n\nhttp://".$GLOBALS['sys_default_domain']."/docman/index.php?group_id=".$this->Group->getID();
+
+			util_send_message('',$subject,$body,'',$BCC);
+		}
+
+		return true;
+	}
+	
+	function delete() {
+		$perm =& $this->Group->getPermission( session_get_user() );
+		if (!$perm || !is_object($perm) || !$perm->isDocEditor()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		
+		$sql = 'DELETE FROM doc_data WHERE docid='.$this->getID();
+		$result = db_query($sql);
+		if (!$result) {
+			$this->setError('Error Deleting Document: '.db_error());
+			db_rollback();
+			return false;
+		}
+		
+		return true;
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/docman/DocumentFactory.class
===================================================================
--- trunk/gforge_base/gforge/common/docman/DocumentFactory.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/docman/DocumentFactory.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,292 @@
+<?php
+/**
+ * GForge Doc Mgr Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: DocumentFactory.class 4192 2005-03-26 04:46:16Z tperdue $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+/*
+	Document Manager
+
+	by Quentin Cregan, SourceForge 06/2000
+
+	Complete OO rewrite by Tim Perdue 1/2003
+*/
+
+require_once('common/include/Error.class');
+require_once('common/docman/Document.class');
+
+class DocumentFactory extends Error {
+
+	/**
+	 * The Group object.
+	 *
+	 * @var	 object  $Group.
+	 */
+	var $Group;
+
+	/**
+	 * The Documents dictionary.
+	 *
+	 * @var	 array	Contains doc_group_id as key and the array of documents associated to that id. 
+	 */
+	var $Documents;
+	var $stateid;
+	var $languageid;
+	var $docgroupid;
+	var $sort='group_name, title';
+
+	/**
+	 *  Constructor.
+	 *
+	 *	@param	object	The Group object to which this DocumentFactory is associated.
+	 *	@return	boolean	success.
+	 */
+	function DocumentFactory(&$Group) {
+		$this->Error();
+		if (!$Group || !is_object($Group)) {
+			$this->setError('ProjectGroup:: No Valid Group Object');
+			return false;
+		}
+		if ($Group->isError()) {
+			$this->setError('ProjectGroup:: '.$Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $Group;
+
+		return true;
+	}
+
+	/**
+	 *	getGroup - get the Group object this DocumentFactory is associated with.
+	 *
+	 *	@return object	the Group object.
+	 */
+	function &getGroup() {
+		return $this->Group;
+	}
+
+	/**
+	 *	setStateID - call this before getDocuments() if you want to limit to a specific state.
+	 *
+	 *	@param	int	The stateid from the doc_states table.
+	 */
+	function setStateID($stateid) {
+		$this->stateid=$stateid;
+	}
+
+	/**
+	 *	setLanguageID - call this before getDocuments() if you want to limit to a specific language.
+	 *
+	 *	@param	int	The language_id from the supported_languages table.
+	 */
+	function setLanguageID($languageid) {
+		$this->languageid=$languageid;
+	}
+
+	/**
+	 *	setDocGroupID - call this before getDocuments() if you want to limit to a specific doc_group.
+	 *
+	 *	@param	int	The doc_group from the doc_groups table.
+	 */
+	function setDocGroupID($docgroupid) {
+		$this->docgroupid=$docgroupid;
+	}
+
+	/**
+	 *	setSort - call this before getDocuments() if you want to control the sorting.
+	 *
+	 *	@param	string	The name of the field to sort on.
+	 */
+	function setSort($sort) {
+		$this->sort=$sort;
+	}
+
+	/**
+	 *	getDocuments - returns an array of Document objects.
+	 *
+	 *	@return	array	Document objects.
+	 */
+	function &getDocuments() {
+		global $Language;
+		if (!$this->Documents) {
+			$this->getFromDB();
+		}
+		
+		$return = array();
+		// If the document group is specified, we should only check that group in
+		// the Documents array. If not, we should check ALL the groups.
+		if ($this->docgroupid) {
+			$keys = array($this->docgroupid);
+		} else {
+			$keys = array_keys($this->Documents);
+		}
+		
+		foreach ($keys as $key) {
+			if (!array_key_exists($key, $this->Documents)) continue;	// Should not happen
+			$count = count($this->Documents[$key]);
+			
+			for ($i=0; $i < $count; $i++) {
+				$valid = true;		// do we need to return this document?
+				$doc =& $this->Documents[$key][$i];
+				
+				if (!$this->stateid) {
+					if (session_loggedin()) {
+						$perm =& $this->Group->getPermission( session_get_user() );
+						if (!$perm || !is_object($perm) || !$perm->isMember()) {
+							if ($doc->getStateID() != 1) {		// non-active document?
+								$valid = false;
+							}
+						} else {
+							if ($doc->getStateID() != 1 &&		/* not active */
+								$doc->getStateID() != 4 &&			/* not hidden */
+								$doc->getStateID() != 5) {			/* not private */
+								$valid = false;
+							}
+						}
+					} else {
+						if ($doc->getStateID() != 1) {		// non-active document?
+							$valid = false;
+						}
+					}
+				} else {
+					if ($this->stateid != "ALL" && $doc->getStateID() != $this->stateid) {
+						$valid = false;
+					}
+				}
+				
+				if ($this->languageid && $doc->getLanguageID() != $this->languageid) {
+					$valid = false;
+				}
+				
+				
+				if ($valid) {
+					$return[] =& $doc;
+				}
+			}
+		}
+		
+		if (count($return) == 0) {
+			$this->setError($Language->getText('docman_common','no_docs'));
+			return false;
+		}
+
+		return $return;
+
+/*
+		if (!$this->stateid) {
+			if (session_loggedin()) {
+				$perm =& $this->Group->getPermission( session_get_user() );
+				if (!$perm || !is_object($perm) || !$perm->isMember()) {
+					$public_flag='AND stateid=1';
+				} else {
+					$public_flag='AND stateid IN (1,4,5)';
+				}
+			} else {
+				$public_flag='AND stateid=1';
+			}
+		} else {
+			if ($this->stateid =='ALL') {
+
+			} else {
+				$public_flag='AND stateid =\''.$this->stateid.'\'';
+			}
+		}
+
+		if ($this->docgroupid) {
+			$docgroupsql="AND doc_group='".$this->docgroupid."'";
+		}
+
+		if ($this->languageid) {
+			$languagesql="AND language_id='".$this->languageid."'";
+		}
+
+		$sql="SELECT *
+			FROM docdata_vw
+			WHERE group_id='". $this->Group->getID() ."' 
+			$public_flag 
+			$docgroupsql
+			$languagesql
+			ORDER BY ".$this->sort;
+
+		$result = db_query ($sql);
+
+		$rows = db_numrows($result);
+
+		if (!$result || $rows < 1) {
+			$this->setError($Language->getText('docman_common','no_docs')." ".db_error());
+			return false;
+		} else {
+			while ($arr =& db_fetch_array($result)) {
+				$this->Documents[] = new Document($this->Group, $arr['docid'], $arr);
+			}
+		}
+		return $this->Documents;
+*/
+	}
+	
+	/**
+	 * getFromDB - Retrieve documents from database.
+	 */
+	function getFromDB() {
+		$this->Documents = array();
+		$sql = 'SELECT * FROM docdata_vw ORDER BY title';
+		$result = db_query($sql);
+		if (!$result) {
+			exit_error('Error', db_error());
+		}
+		
+		while ($arr =& db_fetch_array($result)) {
+			$doc_group_id = $arr['doc_group'];
+			if (!is_array($this->Documents[$doc_group_id])) {
+				$this->Documents[$doc_group_id] = array();
+			}
+			
+			$this->Documents[$doc_group_id][] = new Document($this->Group, $arr['docid'], $arr);
+		}
+	}
+	
+	/**
+	 * getStates - Return an array of states that have documents associated to them
+	 */
+	function getUsedStates() {
+		$sql = "SELECT DISTINCT doc_states.stateid,doc_states.name 
+			FROM doc_states,doc_data
+			WHERE doc_data.stateid=doc_states.stateid
+			ORDER BY doc_states.name ASC";
+		$result = db_query($sql);
+		if (!$result) {
+			exit_error('error', db_error());
+		}
+		
+		$return = array();
+		while ($arr = db_fetch_array($result)) {
+			$return[] = $arr;
+		}
+		
+		return $return;
+	}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/docman/DocumentGroup.class
===================================================================
--- trunk/gforge_base/gforge/common/docman/DocumentGroup.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/docman/DocumentGroup.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,322 @@
+<?php
+/**
+ * GForge Doc Mgr Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: DocumentGroup.class 5148 2005-12-16 17:21:36Z marcelo $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+/*
+	Document Manager
+
+	by Quentin Cregan, SourceForge 06/2000
+
+	Complete OO rewrite by Tim Perdue 1/2003
+*/
+
+require_once('common/include/Error.class');
+
+class DocumentGroup extends Error {
+
+	/**
+	 * The Group object.
+	 *
+	 * @var		object	$Group.
+	 */
+	var $Group; //object
+
+	/**
+	 * Array of data.
+	 *
+	 * @var		array	$data_array.
+	 */
+	var $data_array;
+
+	/**
+	 *  DocumentGroup - constructor.
+	 *
+	 *  Use this constructor if you are modifying an existing doc_group.
+	 *
+	 *	@param	object	Group object.
+	 *  @param	array	(all fields from doc_groups) OR doc_group from database.
+	 *  @return boolean	success.
+	 */
+	function DocumentGroup(&$Group, $data=false) {
+		$this->Error();
+
+		//was Group legit?
+		if (!$Group || !is_object($Group)) {
+			$this->setError('DocumentGroup: No Valid Group');
+			return false;
+		}
+		//did Group have an error?
+		if ($Group->isError()) {
+			$this->setError('DocumentGroup: '.$Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $Group;
+
+		if ($data) {
+			if (is_array($data)) {
+				$this->data_array =& $data;
+//
+//	should verify group_id
+//
+				return true;
+			} else {
+				if (!$this->fetchData($data)) {
+					return false;
+				} else {
+					return true;
+				}
+			}
+		}
+	}
+
+	/**
+	 *	create - create a new item in the database.
+	 *
+	 *	@param	string	Item name.
+	 *  @return id on success / false on failure.
+	 */
+	function create($name,$parent_doc_group=0) {
+		global $Language;
+		//
+		//	data validation
+		//
+		if (!$name) {
+			$this->setError($Language->getText('docman_common_docgroup','name_required'));
+			return false;
+		}
+		
+		if ($parent_doc_group) {
+			// check if parent group exists
+			$res=db_query("SELECT * FROM doc_groups WHERE doc_group='$parent_doc_group' AND group_id=".$this->Group->getID());
+			if (!$res || db_numrows($res) < 1) {
+				$this->setError($Language->getText('docman_common_docgroup','invalid_parent_id'));
+				return false;
+			}
+		} else {
+			$parent_doc_group = 0;
+		}
+
+		$perm =& $this->Group->getPermission (session_get_user());
+		if (!$perm || !$perm->isDocEditor()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		
+		$sql="INSERT INTO doc_groups (group_id,groupname,parent_doc_group)
+			VALUES ('".$this->Group->getID()."','".htmlspecialchars($name)."','".$parent_doc_group."')";
+		$result=db_query($sql);
+		if ($result && db_affected_rows($result) > 0) {
+			$this->clearError();
+		} else {
+			$this->setError('DocumentGroup::create() Error Adding Group: '.db_error());
+			return false;
+		}
+
+		$doc_group = db_insertid($result, 'doc_groups', 'doc_group');
+
+		//	Now set up our internal data structures
+		if (!$this->fetchData($doc_group)) {
+			return false;
+		}
+
+		return true;
+	}
+
+
+	/**
+	 *	fetchData - re-fetch the data for this DocumentGroup from the database.
+	 *
+	 *	@param	int		ID of the doc_group.
+	 *	@return boolean.
+	 */
+	function fetchData($id) {
+		global $Language;
+		$res=db_query("SELECT * FROM doc_groups WHERE doc_group='$id'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError($Language->getText('docman_common_docgroup','invalid_id'));
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	getGroup - get the Group Object this DocumentGroup is associated with.
+	 *
+	 *	@return Object Group.
+	 */
+	function &getGroup() {
+		return $this->Group;
+	}
+
+	/**
+	 *	getID - get this DocumentGroup's ID.
+	 *
+	 *	@return	int	The id #.
+	 */
+	function getID() {
+		return $this->data_array['doc_group'];
+	}
+	
+	/**
+	 *	getID - get parent DocumentGroup's id.
+	 *
+	 *	@return	int	The id #.
+	 */
+	function getParentID() {
+		return $this->data_array['parent_doc_group'];
+	}
+
+	/**
+	 *	getName - get the name.
+	 *
+	 *	@return	String	The name.
+	 */
+	function getName() {
+		return $this->data_array['groupname'];
+	}
+
+	/**
+	 *  update - update a DocumentGroup.
+	 *
+	 *  @param	string	Name of the category.
+	 *  @return boolean.
+	 */
+	function update($name,$parent_doc_group) {
+		global $Language;
+
+		$perm =& $this->Group->getPermission (session_get_user());
+		if (!$perm || !$perm->isDocEditor()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		if (!$name) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		
+		if ($parent_doc_group) {
+			// check if parent group exists
+			$res=db_query("SELECT * FROM doc_groups WHERE doc_group='$parent_doc_group' AND group_id=".$this->Group->getID());
+			if (!$res || db_numrows($res) < 1) {
+				$this->setError($Language->getText('docman_common_docgroup','invalid_parent_id'));
+				return false;
+			}
+		} else {
+			$parent_doc_group=0;
+		}
+
+		$sql="UPDATE doc_groups
+			SET groupname='".htmlspecialchars($name)."',
+			parent_doc_group='".$parent_doc_group."'
+			WHERE doc_group='". $this->getID() ."'
+			AND group_id='".$this->Group->getID()."'";
+		$result=db_query($sql);
+		if ($result && db_affected_rows($result) > 0) {
+			return true;
+		} else {
+			$this->setError(db_error());
+			return false;
+		}
+	}
+		
+	/**
+	* hasDocuments - Recursive function that checks if this group or any of it childs has documents associated to it
+	*
+	* A group has associated documents if and only if there are documents associated to this
+	* group or to any of its childs
+	*
+	* @param array	Array of nested groups information, fetched from DocumentGroupFactory class
+	* @param object	The DocumentFactory object
+	* @param int	(optional) State of the documents
+	*/
+	function hasDocuments(&$nested_groups, &$document_factory, $stateid=0) {
+		static $result = array();	// this function will probably be called several times so we better store results in order to speed things up
+		if (!is_array($result[$stateid])) $result[$stateid] = array();
+		if (array_key_exists($doc_group_id, $result[$stateid])) return $result[$stateid][$doc_group_id];
+
+		$doc_group_id = $this->getID();
+		
+		// check if it has documents
+		if ($stateid) {
+			$document_factory->setStateID($stateid);
+		}
+		$document_factory->setDocGroupID($doc_group_id);
+		$docs = $document_factory->getDocuments();
+		if (is_array($docs) && count($docs) > 0) {		// this group has documents
+			$result[$stateid][$doc_group_id] = true;
+			return true;
+		}
+		
+		// this group doesn't have documents... check recursively on the childs
+		if (is_array($nested_groups["$doc_group_id"])) {
+			$count = count($nested_groups["$doc_group_id"]);
+			for ($i=0; $i < $count; $i++) {
+				if ($nested_groups["$doc_group_id"][$i]->hasDocuments($nested_groups, $document_factory, $stateid)) {
+					// child has documents
+					$result[$stateid][$doc_group_id] = true;
+					return true;
+				}
+			}
+			// no child has documents, then this group doesn't have associated documents
+			$result[$stateid][$doc_group_id] = false;
+			return false;
+		} else {	// this group doesn't have childs
+			$result[$stateid][$doc_group_id] = false;
+			return false;
+		}
+	}
+
+	/**
+	* hasSubgroup - Checks if this group has a specified subgroup associated to it
+	*
+	* @param array Array of nested groups information, fetched from DocumentGroupFactory class
+	* @param int	ID of the subgroup
+	*/
+	function hasSubgroup(&$nested_groups, $doc_subgroup_id) {
+		$doc_group_id = $this->getID();
+
+		if (is_array($nested_groups["$doc_group_id"])) {
+			$count = count($nested_groups["$doc_group_id"]);
+			for ($i=0; $i < $count; $i++) {
+				// child is a match?
+				if ($nested_groups["$doc_group_id"][$i]->getID() == $doc_subgroup_id) {
+					return true;
+				} else {
+					// recursively check if this child has this subgroup
+					if ($nested_groups["$doc_group_id"][$i]->hasSubgroup($nested_groups, $doc_subgroup_id)) {
+						return true;
+					}
+				}
+			}
+		}
+		
+		return false;
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/docman/DocumentGroupFactory.class
===================================================================
--- trunk/gforge_base/gforge/common/docman/DocumentGroupFactory.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/docman/DocumentGroupFactory.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,143 @@
+<?php
+/**
+ * GForge Doc Mgr Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: DocumentGroupFactory.class 4846 2005-10-28 17:17:28Z lcorso $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+/*
+	Document Groups
+*/
+
+require_once('common/include/Error.class');
+require_once('common/forum/ForumMessage.class');
+
+class DocumentGroupFactory extends Error {
+	/**
+	 * This variable holds the document groups
+	 */
+	var $flat_groups;
+
+	/**
+	 * This variable holds the document groups for reading them in nested form
+	 */
+	var $nested_groups;
+
+	/**
+	 * The Group object
+	 */
+	var $Group;
+
+	/**
+	 *  Constructor.
+	 *
+	 *	@return	boolean	success.
+	 */
+	function DocumentGroupFactory(&$Group) {
+		$this->Error();
+		
+		if (!$Group || !is_object($Group)) {
+			$this->setError("DocumentGroupFactory:: Invalid Group");
+			return false;
+		}
+		if ($Group->isError()) {
+			$this->setError('DocumentGroupFactory:: '.$Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $Group;
+
+
+		return true;
+	}
+
+	/**
+	 *	getNested - Return an array of DocumentGroup objects arranged for nested views.
+	 *
+	 *	@return	array	The array of DocumentGroup.
+	 */
+	function &getNested() {
+		if ($this->nested_groups) {
+			return $this->nested_groups;
+		}
+		
+		$sql="SELECT * FROM doc_groups
+		WHERE group_id='".$this->Group->getID()."' 
+		ORDER BY groupname ASC";
+
+		$result=db_query($sql);
+		$rows = db_numrows($result);
+		
+		if (!$result || $rows < 1) {
+			$this->setError('No Groups Found '.db_error());
+			return false;
+		} else {
+			while ($arr = db_fetch_array($result)) {
+				$this->flat_groups[] = new DocumentGroup($this->Group, $arr);
+			}
+		}
+		
+		// Build the nested array
+		$count = count($this->flat_groups);
+		for ($i=0; $i < $count; $i++) {
+			$this->nested_groups["".$this->flat_groups[$i]->getParentID()][] =& $this->flat_groups[$i];
+			
+		}
+
+		
+		return $this->nested_groups;
+
+	}
+		/**
+	 *	getDocumentGroups - Return an array of DocumentGroup objects.
+	 *
+	 *	@return	array	The array of DocumentGroup.
+	 */
+	function &getDocumentGroups() {
+		if ($this->flat_groups) {
+			return $this->flat_groups;
+		}
+		
+		$sql="SELECT * FROM doc_groups
+		WHERE group_id='".$this->Group->getID()."' 
+		ORDER BY groupname ASC";
+
+		$result=db_query($sql);
+		$rows = db_numrows($result);
+		
+		if (!$result || $rows < 1) {
+			$this->setError('No Groups Found '.db_error());
+			return false;
+		} else {
+			while ($arr = db_fetch_array($result)) {
+				$this->flat_groups[] = new DocumentGroup($this->Group, $arr);
+			}
+		}
+		
+
+		
+		return $this->flat_groups;
+
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/forum/Forum.class
===================================================================
--- trunk/gforge_base/gforge/common/forum/Forum.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/forum/Forum.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,823 @@
+<?php
+/**
+ * GForge Forums Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: Forum.class 5037 2005-11-28 20:04:49Z lcorso $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+/*
+	Message Forums
+	By Tim Perdue, Sourceforge, 11/99
+
+	Massive rewrite by Tim Perdue 7/2000 (nested/views/save)
+
+	Complete OO rewrite by Tim Perdue 12/2002
+*/
+
+require_once('common/include/Error.class');
+require_once('common/forum/ForumMessage.class');
+// This string is used when sending the notification mail for identifying the
+// user response
+define('FORUM_MAIL_MARKER', '#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+');	
+
+class Forum extends Error {
+
+	/**
+	 * Associative array of data from db.
+	 *
+	 * @var	 array   $data_array.
+	 */
+	var $data_array;
+
+	/**
+	 * The Group object.
+	 *
+	 * @var	 object  $Group.
+	 */
+	var $Group; //group object
+
+	/**
+	 * An array of 'types' for this forum - nested, flat, ultimate, etc.
+	 *
+	 * @var	 array	view_types.
+	 */
+	var $view_types;
+
+	var $current_user_perm;
+
+	/**
+	 *  Constructor.
+	 *
+	 *	@param	object	The Group object to which this forum is associated.
+	 *  @param  int	 The group_forum_id.
+	 *  @param  array	The associative array of data.
+	 *	@return	boolean	success.
+	 */
+	function Forum(&$Group, $group_forum_id=false, $arr=false) {
+		global $Language;
+		$this->Error();
+		if (!$Group || !is_object($Group)) {
+			$this->setError($Language->getText('general','error_no_valid_group_object','Forum'));
+			return false;
+		}
+		if ($Group->isError()) {
+			$this->setError('Forum:: '.$Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $Group;
+
+		if ($group_forum_id) {
+			if (!$arr || !is_array($arr)) {
+				if (!$this->fetchData($group_forum_id)) {
+					return false;
+				}
+			} else {
+				$this->data_array =& $arr;
+				if ($this->data_array['group_id'] != $this->Group->getID()) {
+					$this->setError($Language->getText('general','error_group_id'));
+					$this->data_array = null;
+					return false;
+				}
+			}
+			//
+			//	Make sure they can even access this object
+			//
+			if (!$this->userCanView()) {
+				$this->setPermissionDeniedError();
+				$this->data_array = null;
+				return false;
+			}
+		}
+		$this->view_types[]='ultimate';
+		$this->view_types[]='flat';
+		$this->view_types[]='nested';
+		$this->view_types[]='threaded';
+		return true;
+	}
+
+	/**
+	 *	create - use this function to create a new entry in the database.
+	 *
+	 *	@param	string	The name of the forum.
+	 *	@param	string	The description of the forum.
+	 *	@param	int	Pass (1) if it should be public (0) for private.
+	 *	@param	string	The email address to send all new posts to.
+	 *	@param	int	Pass (1) if a welcome message should be created (0) for no welcome message.
+	 *	@param	int	Pass (1) if we should allow non-logged-in users to post (0) for mandatory login.
+	 *	@return	boolean	success.
+	 */
+	function create($forum_name,$description,$is_public=1,$send_all_posts_to='',$create_default_message=1,$allow_anonymous=1) {
+		global $Language;
+		if (strlen($forum_name) < 3) {
+			$this->setError($Language->getText('forum_common','error_min_name_length'));
+			return false;
+		}
+		if (strlen($description) < 10) {
+			$this->setError($Language->getText('forum_common','error_min_desc_length'));
+			return false;
+		}
+		if (eregi('[^_\.0-9a-z-]',$forum_name)) {
+			$this->setError($Language->getText('forum_common','error_illegal_characters'));
+			return false;
+		}
+		if ($send_all_posts_to) {
+			$invalid_mails = validate_emails($send_all_posts_to);
+			if (count($invalid_mails) > 0) {
+				$this->setInvalidEmailError();
+				return false;
+			}
+		}
+
+
+		// This is a hack to allow non-site-wide-admins to post
+		// news.  The news/submit.php checks for proper permissions.
+		// This needs to be revisited.
+		global $sys_news_group;
+		if ($this->Group->getID() == $sys_news_group) {
+			// Future check will be added.
+
+		} else {
+			// Current permissions check.
+
+			$perm =& $this->Group->getPermission( session_get_user() );
+
+			if (!$perm || !is_object($perm) || !$perm->isForumAdmin()) {
+				$this->setPermissionDeniedError();
+				return false;
+			}
+		}
+
+		$sql="INSERT INTO forum_group_list (group_id,forum_name,is_public,description,send_all_posts_to,allow_anonymous)
+			VALUES ('".$this->Group->getId()."',
+			'". strtolower($forum_name) ."',
+			'$is_public',
+			'". htmlspecialchars($description) ."',
+			'$send_all_posts_to',
+			'$allow_anonymous')";
+
+		db_begin();
+		$result=db_query($sql);
+		if (!$result) {
+			db_rollback();
+			$this->setError($Language->getText('forum_common','error_adding_forum').db_error());
+			return false;
+		}
+		$this->group_forum_id=db_insertid($result,'forum_group_list','group_forum_id');
+		$this->fetchData($this->group_forum_id);
+		if (!$this->addAllUsers()) {
+			db_rollback();
+			return false;
+		}
+
+		if ($create_default_message) {
+			$fm=new ForumMessage($this);
+			if (!$fm->create("Welcome to ".$forum_name,"Welcome to ".$forum_name)) {
+				$this->setError($fm->getErrorMessage());
+				return false;
+			}
+		}
+		db_commit();
+		return true;
+	}
+
+	/**
+	 *  fetchData - re-fetch the data for this forum from the database.
+	 *
+	 *  @param  int	 The forum_id.
+	 *	@return	boolean	success.
+	 */
+	function fetchData($group_forum_id) {
+		global $Language;
+		$res=db_query("SELECT * FROM forum_group_list_vw
+			WHERE group_forum_id='$group_forum_id'
+			AND group_id='". $this->Group->getID() ."'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError($Language->getText('forum_common','error_invalid_group_forum_id'));
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	getGroup - get the Group object this ArtifactType is associated with.
+	 *
+	 *	@return	object	The Group object.
+	 */
+	function &getGroup() {
+		return $this->Group;
+	}
+
+	/**
+	 *	getID - The id of this forum.
+	 *
+	 *	@return	int	The group_forum_id #.
+	 */
+	function getID() {
+		return $this->data_array['group_forum_id'];
+	}
+
+	/**
+	 *	getNextThreadID - The next thread_id for a new top in this forum.
+	 *
+	 *	@return	int	The next thread_id #.
+	 */
+	function getNextThreadID() {
+		$result=db_query("SELECT nextval('forum_thread_seq')");
+		if (!$result || db_numrows($result) < 1) {
+			echo db_error();
+			return false;
+		} else {
+			return db_result($result,0,0);
+		}
+	}
+
+	/**
+	 * getUnixName - returns the name used by email gateway
+	 *
+	 * @return string unix name
+	 */
+	function getUnixName() {
+		return $this->Group->getUnixName().'-'.$this->getName();
+	}
+
+	/**
+	 *	getSavedDate - The unix time when the person last hit "save my place".
+	 *
+	 *	@return	int	The unix time.
+	 */
+	function getSavedDate() {
+		if ($this->save_date) {
+			return $this->save_date;
+		} else {
+			if (session_loggedin()) {
+				$sql="SELECT save_date FROM forum_saved_place
+					WHERE user_id='".user_getid()."' AND forum_id='". $this->getID() ."';";
+				$result = db_query($sql);
+				if ($result && db_numrows($result) > 0) {
+					$this->save_date=db_result($result,0,'save_date');
+					return $this->save_date;
+				} else {
+					//highlight new messages from the past week only
+					$this->save_date=(time()-604800);
+					return $this->save_date;
+				}
+			} else {
+				//highlight new messages from the past week only
+				$this->save_date=(time()-604800);
+				return $this->save_date;
+			}
+		}
+	}
+
+	/**
+	 *	allowAnonymous - does this forum allow non-logged in users to post.
+	 *
+	 *	@return boolean	allow_anonymous.
+	 */
+	function allowAnonymous() {
+		return $this->data_array['allow_anonymous'];
+	}
+
+	/**
+	 *	isPublic - Is this forum open to the general public.
+	 *
+	 *	@return boolean	is_public.
+	 */
+	function isPublic() {
+		return $this->data_array['is_public'];
+	}
+
+	/**
+	 *	getName - get the name of this forum.
+	 *
+	 *	@return string	The name of this forum.
+	 */
+	function getName() {
+		return $this->data_array['forum_name'];
+	}
+
+	/**
+	 *	getSendAllPostsTo - an optional email address to send all forum posts to.
+	 *
+	 *	@return string	The email address.
+	 */
+	function getSendAllPostsTo() {
+		return $this->data_array['send_all_posts_to'];
+	}
+
+	/**
+	 *	getDescription - the description of this forum.
+	 *
+	 *	@return string	The description.
+	 */
+	function getDescription() {
+		return $this->data_array['description'];
+	}
+
+	/**
+	 *	getMessageCount - the total number of messages in this forum.
+	 *
+	 *	@return int	The count.
+	 */
+	function getMessageCount() {
+		return $this->data_array['total'];
+	}
+
+	/**
+	 *	getThreadCount - the total number of threads in this forum.
+	 *
+	 *	@return int	The count.
+	 */
+	function getThreadCount() {
+		return $this->data_array['threads'];
+	}
+
+	/**
+	 *	getMostRecentDate - the most recent date of a post to this board.
+	 *
+	 *	@return int	The most recent date.
+	 */
+	function getMostRecentDate() {
+		return $this->data_array['recent'];
+	}
+
+	/**
+	 *	getMonitoringIDs - return an array of user_id's for those monitoring this forum.
+	 *
+	 *	@return	array	The array of user_id's.
+	 */
+	function getMonitoringIDs() {
+		$sql="SELECT user_id FROM forum_monitored_forums WHERE forum_id='".$this->getID()."'";
+		$result=db_query($sql);
+		return util_result_column_to_array($result);
+	}
+	
+	/**
+	 * getReturnEmailAddress() - return the return email address for notification emails
+	 *
+	 * @return string return email address
+	 */
+	function getReturnEmailAddress() {
+		global $sys_default_domain, $sys_use_gateways;
+		$address = '';
+		if($sys_use_gateways) {
+			$address .= $this->getUnixName();
+		} else {
+			$address .= 'noreply';
+		}
+		$address .= '@';
+		if($sys_use_gateways && isset($GLOBALS['sys_forum_return_domain'])) {
+			$address .= $GLOBALS['sys_forum_return_domain'];
+		} else {
+			$address .= $sys_default_domain;
+		}
+		return $address;
+	}
+
+	/**
+	 *	setMonitor - Add the current user to the list of people monitoring the forum.
+	 *
+	 *	@return	boolean	success.
+	 */
+	function setMonitor() {
+		global $Language;
+		if (!session_loggedin()) {
+			$this->setError($Language->getText('forum_common','error_set_monitor'));
+			return false;
+		}
+		$sql="SELECT * FROM forum_monitored_forums
+			WHERE user_id='".user_getid()."' AND forum_id='".$this->getID()."';";
+		$result = db_query($sql);
+
+		if (!$result || db_numrows($result) < 1) {
+			/*
+				User is not already monitoring thread, so
+				insert a row so monitoring can begin
+			*/
+			$sql="INSERT INTO forum_monitored_forums (forum_id,user_id)
+				VALUES ('".$this->getID()."','".user_getid()."')";
+
+			$result = db_query($sql);
+
+			if (!$result) {
+				$this->setError($Language->getText('forum_common','error_unable_to_add_monitor').' : '.db_error());
+				return false;
+			}
+
+		}
+		return true;
+	}
+
+	/**
+	 *	stopMonitor - Remove the current user from the list of people monitoring the forum.
+	 *
+	 *	@return	boolean	success.
+	 */
+	function stopMonitor() {
+		global $Language;
+		if (!session_loggedin()) {
+			$this->setError($Language->getText('forum_common','error_set_monitor'));
+			return false;
+		}
+		$sql="DELETE FROM forum_monitored_forums
+			WHERE user_id='".user_getid()."' AND forum_id='".$this->getID()."';";
+		return db_query($sql);
+	}
+
+	/**
+	 *	isMonitoring - See if the current user is in the list of people monitoring the forum.
+	 *
+	 *	@return	boolean	is_monitoring.
+	 */
+	function isMonitoring() {
+		if (!session_loggedin()) {
+			return false;
+		}
+		$sql="SELECT count(*) FROM forum_monitored_forums WHERE user_id='".user_getid()."' AND forum_id='".$this->getID()."';";
+		$result = db_query($sql);
+		$row_count = db_fetch_array($result);
+		return $result && $row_count['count'] > 0;
+	}
+
+	/**
+	 *	savePlace - set a unix time into the database for this user, so future messages can be highlighted.
+	 *
+	 *	@return	boolean	success.
+	 */
+	function savePlace() {
+		global $Language;
+		if (!session_loggedin()) {
+			$this->setError($Language->getText('forum_common','error_save_when_logged_in'));
+			return false;
+		}
+		$sql="SELECT * FROM forum_saved_place
+			WHERE user_id='".user_getid()."' AND forum_id='".$this->getID()."'";
+
+		$result = db_query($sql);
+
+		if (!$result || db_numrows($result) < 1) {
+			/*
+				User is not already monitoring thread, so
+				insert a row so monitoring can begin
+			*/
+			$sql="INSERT INTO forum_saved_place (forum_id,user_id,save_date)
+				VALUES ('".$this->getID()."','".user_getid()."','".time()."')";
+
+			$result = db_query($sql);
+
+			if (!$result) {
+				$this->setError($Language->getText('forum_common_forum','error_save_place').': '.db_error());
+				return false;
+			}
+
+		} else {
+			$sql="UPDATE forum_saved_place
+				SET save_date='".time()."'
+				WHERE user_id='".user_getid()."' AND forum_id='".$this->getID()."'";
+			$result = db_query($sql);
+
+			if (!$result) {
+				$this->setError('Forum::savePlace() '.db_error());
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 *	update - use this function to update an entry in the database.
+	 *
+	 *	@param	string	The name of the forum.
+	 *	@param	string	The description of the forum.
+	 *	@param	int	Pass (1) if it should be public (0) for private.
+	 *	@param	string	The email address to send all new posts to.
+	 *	@param	int	Pass (1) if we should allow non-logged-in users to post (0) for mandatory login.
+	 *	@return	boolean	success.
+	 */
+	function update($forum_name,$description,$send_all_posts_to='') {
+		global $Language;
+		if (strlen($forum_name) < 3) {
+			$this->setError($Language->getText('forum_common','error_min_name_length'));
+			return false;
+		}
+		if (strlen($description) < 10) {
+			$this->setError($Language->getText('forum_common','error_min_desc_length'));
+			return false;
+		}
+		if (eregi('[^_\.0-9a-z-]',$forum_name)) {
+			$this->setError($Language->getText('forum_common','error_illegal_characters'));
+			return false;
+		}
+		if ($send_all_posts_to) {
+			$invalid_mails = validate_emails($send_all_posts_to);
+			if (count($invalid_mails) > 0) {
+				$this->setInvalidEmailError();
+				return false;
+			}
+		}
+
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		$res=db_query("UPDATE forum_group_list SET
+			forum_name='". strtolower($forum_name) ."',
+			description='". htmlspecialchars($description) ."',
+			send_all_posts_to='$send_all_posts_to'
+			WHERE group_id='".$this->Group->getID()."'
+			AND group_forum_id='".$this->getID()."'");
+
+		if (!$res || db_affected_rows($res) < 1) {
+			$this->setError($Language->getText('general','error_on_update').': '.db_error());
+			return false;
+		}
+		return true;
+	}
+
+	/**
+	 *  delete - delete this forum and all its related data.
+	 *
+	 *  @param  bool	I'm Sure.
+	 *  @param  bool	I'm REALLY sure.
+	 *  @return   bool true/false;
+	 */
+	function delete($sure, $really_sure) {
+		if (!$sure || !$really_sure) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		db_begin();
+		db_query("DELETE FROM forum_agg_msg_count
+			WHERE group_forum_id='".$this->getID()."'");
+//echo '1'.db_error();
+		db_query("DELETE FROM forum_monitored_forums
+			WHERE forum_id='".$this->getID()."'");
+//echo '2'.db_error();
+		db_query("DELETE FROM forum_saved_place
+			WHERE forum_id='".$this->getID()."'");
+//echo '3'.db_error();
+		db_query("DELETE FROM forum
+			WHERE group_forum_id='".$this->getID()."'");
+//echo '4'.db_error();
+		db_query("DELETE FROM forum_group_list
+			WHERE group_forum_id='".$this->getID()."'");
+//echo '5'.db_error();
+		db_commit();
+		return true;
+	}
+
+	/**
+	 *	addAllUsers - add all users to this forum.
+	 *
+	 *	@return boolean success.
+	 */
+	function addAllUsers() {
+		global $sys_news_group;
+		if ($this->Group->getID() == $sys_news_group) {
+			return true;
+		}
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		$sql="INSERT INTO forum_perm (group_forum_id,user_id,perm_level)
+			SELECT '".$this->getID()."',user_id,forum_flags
+			FROM user_group
+			WHERE 
+			group_id='".$this->Group->getID()."'
+			AND NOT EXISTS (SELECT user_id FROM forum_perm
+			WHERE group_forum_id='".$this->getID()."'
+			AND user_id=user_group.user_id);";
+		$res= db_query($sql);
+		if (!$res) {
+			$this->setError(db_error());
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 *  addUser - add a user to this subproject.
+	 *
+	 *  @param  int  user_id of the new user.
+	 *  @return boolean success.
+	 */
+	function addUser($id) {
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		if (!$id) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		$sql="SELECT * FROM forum_perm
+			WHERE group_forum_id='".$this->getID()."'
+			AND user_id='$id'";
+		$result=db_query($sql);
+		if (db_numrows($result) > 0) {
+			return true;
+		} else {
+			$sql="INSERT INTO forum_perm (group_forum_id,user_id,perm_level)
+				VALUES ('".$this->getID()."','$id',0)";
+			$result=db_query($sql);
+			if ($result && db_affected_rows($result) > 0) {
+				return true;
+			} else {
+				$this->setError(db_error());
+				return false;
+			}
+		}
+	}
+
+	/**
+	 *  updateUser - update a user's permissions.
+	 *
+	 *  @param  int  user_id of the user to update.
+	 *  @param  int  (0) read only, (1) tech only, (2) admin & tech (3) admin only.
+	 *  @return boolean success.
+	 */
+	function updateUser($id,$perm_level) {
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		if (!$id) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		//
+		//	Update and test if it already exists
+		//
+		$sql="UPDATE forum_perm SET perm_level='$perm_level'
+			WHERE user_id='$id' AND group_forum_id='".$this->getID()."'";
+		$result=db_query($sql);
+		if (db_affected_rows($result) < 1) {
+			//
+			//	If not, insert it.
+			//
+			$sql="INSERT INTO forum_perm (group_forum_id,user_id,perm_level) VALUES 
+				('".$this->getID()."','$id','$perm_level')";
+			$result=db_query($sql);
+			if (!$result) {
+				$this->setError(db_error());
+				return false;
+			} else {
+				return true;
+			}
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 *  deleteUser - delete a user's permissions.
+	 *
+	 *  @param  int  user_id of the user who's permissions to delete.
+	 *  @return boolean success.
+	 */
+	function deleteUser($id) {
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		if (!$id) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		$sql="DELETE FROM forum_perm
+			WHERE user_id='$id' AND group_forum_id='".$this->getID()."'";
+		$result=db_query($sql);
+		if ($result) {
+			return true;
+		} else {
+			$this->setError(db_error());
+			return false;
+		}
+	}
+
+
+	/*
+
+		USER PERMISSION FUNCTIONS
+
+	*/
+
+	/**
+	 *  userCanView - determine if the user can view this subproject.
+	 *
+	 *  @return boolean   user_can_view.
+	 */
+	function userCanView() {
+		if ($this->isPublic()) {
+			return true;
+		} else {
+			if (!session_loggedin()) {
+				return false;
+			} else {
+				//
+				//  You must have an entry in project_perm if this subproject is not public
+				//
+				if ($this->getCurrentUserPerm() >= 0) {
+					return true;
+				} else {
+					return false;
+				}
+			}
+		}
+	}
+
+	/**
+	 *  userCanPost - see if the logged-in user's perms are >= 1 or Group ForumAdmin.
+	 *
+	 *  @return boolean user_can_post.
+	 */
+	function userCanPost() {
+		if (($this->isPublic() && $this->allowAnonymous()) || $this->userIsAdmin()) {
+			return true;
+		} elseif ($this->isPublic() && session_loggedin()) {
+			return true;
+		} else {
+			if (!session_loggedin()) {
+				return false;
+			} else {
+				if ($this->getCurrentUserPerm() >= 1) {
+					return true;
+				} else {
+					return false;
+				}
+			}
+		}
+	}
+
+	/**
+	 *  userIsAdmin - see if the logged-in user's perms are >= 2 or Group ForumAdmin.
+	 *
+	 *  @return boolean user_is_admin.
+	 */
+	function userIsAdmin() {
+		if (!session_loggedin()) {
+				return false;
+		} else {
+			$perm =& $this->Group->getPermission( session_get_user() );
+
+			if (($this->getCurrentUserPerm() >= 2) || ($perm->isForumAdmin())) {
+				return true;
+			} else {
+				return false;
+			}
+		}
+	}
+
+	/**
+	 *  getCurrentUserPerm - get the logged-in user's perms from forum_perm.
+	 *
+	 *  @return int perm level for the logged-in user.
+	 */
+	function getCurrentUserPerm() {
+		if (!session_loggedin()) {
+			return -1;
+		} else {
+			if (!isset($this->current_user_perm)) {
+				$sql="select perm_level
+				FROM forum_perm
+				WHERE group_forum_id='". $this->getID() ."'
+				AND user_id='".user_getid()."'";
+				$this->current_user_perm=db_result(db_query($sql),0,0);
+			}
+			return $this->current_user_perm;
+		}
+	}
+
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/forum/ForumFactory.class
===================================================================
--- trunk/gforge_base/gforge/common/forum/ForumFactory.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/forum/ForumFactory.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,140 @@
+<?php
+/**
+ * GForge Forums Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: ForumFactory.class 4673 2005-09-19 16:52:28Z danper $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+/*
+	Message Forums
+	By Tim Perdue, Sourceforge, 11/99
+
+	Massive rewrite by Tim Perdue 7/2000 (nested/views/save)
+
+	Complete OO rewrite by Tim Perdue 12/2002
+*/
+
+
+require_once('common/include/Error.class');
+require_once('common/forum/Forum.class');
+
+class ForumFactory extends Error {
+
+	/**
+	 * The Group object.
+	 *
+	 * @var	 object  $Group.
+	 */
+	var $Group;
+
+	/**
+	 * The forums array.
+	 *
+	 * @var	 array	forums.
+	 */
+	var $forums;
+
+	/**
+	 *  Constructor.
+	 *
+	 *	@param	object	The Group object to which this forum is associated.
+	 */
+	function ForumFactory(&$Group) {
+		global $Language;
+		$this->Error();
+		if (!$Group || !is_object($Group)) {
+			$this->setError($Language->getText('forum','error_no_valid_group_object'));
+			return false;
+		}
+		if ($Group->isError()) {
+			$this->setError($Language->getText('forum','forum').':: '.$Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $Group;
+
+		return true;
+	}
+
+	/**
+	 *	getGroup - get the Group object this ForumFactory is associated with.
+	 *
+	 *	@return object	The Group object.
+	 */
+	function &getGroup() {
+		return $this->Group;
+	}
+
+	/**
+	 *	getForums - get an array of Forum objects for this Group.
+	 *
+	 *	@return	array	The array of Forum objects.
+	 */
+	function &getForums() {
+		global $Language;
+		if ($this->forums) {
+			return $this->forums;
+		}
+		if (session_loggedin()) {
+			$perm =& $this->Group->getPermission( session_get_user() );
+			if (!$perm || !is_object($perm) || !$perm->isMember()) {
+				$public_flag='=1';
+			} else {
+				$public_flag='<3';
+				if ($perm->isForumAdmin()) {
+					$exists='';
+				} else {
+					$exists=" AND group_forum_id IN (SELECT group_forum_ID
+					FROM forum_perm
+					WHERE perm_level >= 0 AND group_forum_id=forum_group_list.group_forum_id
+					AND user_id='".user_getid()."') ";
+				}
+			}
+		} else {
+			$public_flag='=1';
+		}
+
+		$sql="SELECT *
+			FROM forum_group_list_vw
+			WHERE group_id='". $this->Group->getID() ."' 
+			AND is_public $public_flag 
+			$exists
+			ORDER BY group_forum_id;";
+
+		$result = db_query ($sql);
+
+		$rows = db_numrows($result);
+
+		if (!$result) {
+			$this->setError($Language->getText('forum_common_forumfactory','error_no_forums_found').db_error());
+			return false;
+		} else {
+			while ($arr = db_fetch_array($result)) {
+				$this->forums[] = new Forum($this->Group, $arr['group_forum_id'], $arr);
+			}
+		}
+		return $this->forums;
+	}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/forum/ForumMessage.class
===================================================================
--- trunk/gforge_base/gforge/common/forum/ForumMessage.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/forum/ForumMessage.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,439 @@
+<?php
+/**
+ * GForge Forums Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: ForumMessage.class 5632 2006-08-16 14:35:25Z federicot $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+/*
+	Message Forums
+	By Tim Perdue, Sourceforge, 11/99
+
+	Massive rewrite by Tim Perdue 7/2000 (nested/views/save)
+
+	Complete OO rewrite by Tim Perdue 12/2002
+*/
+
+require_once('common/include/Error.class');
+
+class ForumMessage extends Error {
+
+	/**
+	 * Associative array of data from db.
+	 *
+	 * @var	 array   $data_array.
+	 */
+	var $data_array;
+
+	/**
+	 * The Forum object.
+	 *
+	 * @var	 object  $Forum.
+	 */
+	var $Forum;
+	
+		/**
+	 *  Constructor.
+	 *
+	 *	@param	object	The Forum object to which this ForumMessage is associated.
+	 *  @param  int	 The message_id.
+	 *  @param  array   The associative array of data.
+	 *	@return	boolean	success.
+	 */
+	function ForumMessage(&$Forum, $msg_id=false, $arr=false) {
+		global $Language;
+		$this->Error();
+		if (!$Forum || !is_object($Forum)) {
+			$this->setError($Language->getText('forum_common_forummessage','no_valid_forum_object'));
+			return false;
+		}
+		if ($Forum->isError()) {
+			$this->setError('ForumMessage:: '.$Forum->getErrorMessage());
+			return false;
+		}
+		$this->Forum =& $Forum;
+
+		if ($msg_id) {
+			if (!$arr || !is_array($arr)) {
+				if (!$this->fetchData($msg_id)) {
+					return false;
+				}
+			} else {
+				$this->data_array =& $arr;
+				//
+				//	Verify this message truly belongs to this Forum
+				//
+				if ($this->data_array['group_forum_id'] != $this->Forum->getID()) {
+					$this->setError($Language->getText('forum_common_forummessage','error_group_forum_id'));
+					$this->data_array=null;
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+
+	/**
+	 *	create - use this function to create a new message in the database.
+	 *
+	 *	@param	string	The subject of the message.
+	 *	@param	string	The body of the message.
+	 *	@param	int	The thread_id of the message, if known.
+	 *	@param	int	The message_id of the parent message, if any.
+	 *	@return	boolean success.
+	 */
+	function create($subject, $body, $thread_id='', $is_followup_to='') {
+		global $Language;
+		if (!$body || !$subject) {
+			$this->setError($Language->getText('forum_common_forummessage','error_required_fields'));
+			return false;
+		}
+		if (!$this->Forum->userCanPost()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		if (!session_loggedin()) {
+			$user_id=100;
+		} else {
+			$user_id=user_getid();
+		}
+
+		if ($is_follow_up_to) {
+			$ParentMessage=new ForumMessage($this->Forum,$is_followup_to);
+			if (!$ParentMessage || !is_object($ParentMessage)) {
+				$this->setError("ForumMessage::create()".$Language->getText('forum_common_forummessage','error_no_valid_parent_message'));
+				return false;
+			}
+			if ($ParentMessage->isError()) {
+				$this->setError('ForumMessage::create() '.$ParentMessage->getErrorMessage());
+				return false;
+			}
+		}
+		if (!$is_followup_to) { 
+			$is_followup_to=0; 
+		}
+
+		//see if that message has been posted already for all the idiots that double-post
+		$res3=db_query("SELECT * FROM forum 
+			WHERE is_followup_to='$is_followup_to' 
+			AND body='".  htmlspecialchars($body) ."'
+			AND subject='".  htmlspecialchars($subject) ."' 
+			AND group_forum_id='". $this->Forum->getId() ."' 
+			AND posted_by='$user_id'");
+
+		if (db_numrows($res3) > 0) {
+			//already posted this message
+			$this->setError($Language->getText('forum_utils','doublepost'));
+			return false;
+		} else {
+			echo db_error();
+		}
+		db_begin();
+		if (!$thread_id) {
+			$thread_id=$this->Forum->getNextThreadID();
+			$is_followup_to=0;
+			if (!$thread_id) {
+				$this->setError('ForumMessage::create() '.$Language->getText('forum_common_forummessage','error_getting_next_thread'));
+				db_rollback();
+				return false;
+			}
+		} else {
+			//
+			//  increment the parent's followup count if necessary
+			//
+			$res4=db_query("UPDATE forum SET most_recent_date='". time() ."' 
+				WHERE thread_id='$thread_id' AND is_followup_to='0'");
+			if (!$res4 || db_affected_rows($res4) < 1) {
+				$this->setError($Language->getText('forum_common_forummessage','error_cannot_update_master_thread'));
+				db_rollback();
+				return false;
+			} else {
+				//
+				//  mark the parent with followups as an optimization later
+				//
+				$res3=db_query("UPDATE forum SET has_followups='1',most_recent_date='". time() ."' 
+					WHERE msg_id='$is_followup_to'");
+				if (!$res3) {
+					$this->setError($Language->getText('forum_common_forummessage','error_cannot_update_parent'));
+					db_rollback();
+					return false;
+				}
+			}
+		}
+
+		$sql="INSERT INTO forum (group_forum_id,posted_by,subject,
+			body,post_date,is_followup_to,thread_id,most_recent_date) 
+			VALUES ('". $this->Forum->getID() ."', '$user_id', '". htmlspecialchars($subject) ."', 
+			'". htmlspecialchars($body) ."', '". time() ."','$is_followup_to','$thread_id','". time() ."')";
+
+		$result=db_query($sql);
+		if (!$result || db_affected_rows($result) < 1) {
+			$this->setError($Language->getText('forum_common_forummessage','error_posting_failed').' '.db_error());
+			db_rollback();
+			return false;
+		} else {
+			$msg_id=db_insertid($result,'forum','msg_id');
+			if (!$this->fetchData($msg_id)) {
+				db_rollback();
+				return false;
+			}
+			if (!$msg_id) {
+				db_rollback();
+				$this->setError($Language->getText('forum_common_forummessage','error_get_message_id'));
+				return false;
+			} else {
+				if (!$this->sendNotice()) {
+					db_rollback();
+					return false;
+				}
+//echo "Committing";
+				db_commit();
+//echo "db_error()".db_error();
+				return true;
+			}
+		}
+	}
+
+	/**
+	 *  fetchData - re-fetch the data for this forum_message from the database.
+	 *
+	 *  @param  int	 The message ID.
+	 *  @return boolean	success.
+	 */
+	function fetchData($msg_id) {
+		global $Language;
+		$res=db_query("SELECT * FROM forum_user_vw
+			WHERE msg_id='$msg_id'
+			AND group_forum_id='". $this->Forum->getID() ."'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError($Language->getText('forum_common_forummessage','error_invalid_message_id').db_error());
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	getForum - get the Forum object this ForumMessage is associated with.
+	 *
+	 *	@return	object	The Forum object.
+	 */
+	function &getForum() {
+		return $this->Forum;
+	}
+
+	/**
+	 *	getID - get this message_id.
+	 *
+	 *	@return	int	The message_id.
+	 */
+	function getID() {
+		return $this->data_array['msg_id'];
+	}
+
+	/**
+	 *	getPosterName - get the unix user_name of this message's poster.
+	 *
+	 *	@return	string	The poster's unix name.
+	 */
+	function getPosterName() {
+		return $this->data_array['user_name'];
+	}
+
+	/**
+	 *	getPosterID - get this user_id of this message's poster.
+	 *
+	 *	@return	int	The user_id.
+	 */
+	function getPosterID() {
+		return $this->data_array['posted_by'];
+	}
+
+	/**
+	 *	getPosterRealName - get the real name of this message's poster.
+	 *
+	 *	@return	string	The real name.
+	 */
+	function getPosterRealName() {
+		return $this->data_array['realname'];
+	}
+
+	/**
+	 *	getSubject - get the subject of this message.
+	 *
+	 *	@return	string	The subject.
+	 */
+	function getSubject() {
+		return $this->data_array['subject'];
+	}
+
+	/**
+	 *	getBody - get the body of this message.
+	 *
+	 *	@return	String	The body.
+	 */
+	function getBody() {
+		return $this->data_array['body'];
+	}
+
+	/**
+	 *	getPostDate - get the post date of this message.
+	 *
+	 *	@return	int	The post date.
+	 */
+	function getPostDate() {
+		return $this->data_array['post_date'];
+	}
+
+	/**
+	 *	getParentID - get the id of the parent message, if this is a followup.
+	 *
+	 *	@return	int	The parent id.
+	 */
+	function getParentID() {
+		return $this->data_array['is_followup_to'];
+	}
+
+	/**
+	 *	getThreadID - get the thread_id of the message.
+	 *
+	 *	@return	int	The thread_id.
+	 */
+	function getThreadID() {
+		return $this->data_array['thread_id'];
+	}
+
+	/**
+	 *	getMostRecentDate - get the date of the most recent followup.
+	 *
+	 *	@return	int	The date of the most recent followup.
+	 */
+	function getMostRecentDate() {
+		return $this->data_array['most_recent_date'];
+	}
+
+	/**
+	 *	hasFollowups - whether this message has any followups.
+	 *
+	 *	@return boolean has_followups.
+	 */
+	function hasFollowups() {
+		return $this->data_array['has_followups'];
+	}
+
+	/**
+	 *	delete - Delete this message and its followups.
+	 *
+	 *	@return	int	The count of deleted messages.
+	 */
+	function delete() {
+		$msg_id=$this->getID();
+		$perm =& $this->Forum->Group->getPermission( session_get_user() );
+
+		if (!$perm || !is_object($perm) || !$perm->isForumAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		$sql="SELECT msg_id FROM forum 
+			WHERE is_followup_to='$msg_id' 
+			AND group_forum_id='".$this->Forum->getID()."'";
+		$result=db_query($sql);
+		$rows=db_numrows($result);
+		$count=1;
+
+		for ($i=0;$i<$rows;$i++) {
+			$msg = new ForumMessage($this->Forum,db_result($result,$i,'msg_id'));
+			$count += $msg->delete();
+		}
+		$sql="DELETE FROM forum 
+			WHERE msg_id='$msg_id' 
+			AND group_forum_id='".$this->Forum->getID()."'";
+		$toss=db_query($sql);
+
+		return $count;
+
+	}
+
+	/**
+	 *	sendNotice - contains the logic to send out email followups when a message is posted.
+	 *
+	 *	@return boolean success.
+	 */
+	function sendNotice() {
+		global $Language;
+		$ids =& $this->Forum->getMonitoringIDs();
+
+		//
+		//	See if there is anyone to send messages to
+		//
+		if (!count($ids) > 0 && !$this->Forum->getSendAllPostsTo()) {
+			return true;
+		}
+
+		$body = "\nRead and respond to this message at: ".
+		"\nhttp://".$GLOBALS['sys_default_domain']."/forum/message.php?msg_id=".$this->getID().
+		"\nOr by replying to this e-mail entering your response between the following markers: ".
+			"\n".FORUM_MAIL_MARKER.
+			"\n(enter your response here)".
+			"\n".FORUM_MAIL_MARKER.
+			"\n\n".
+		"\nBy: " . $this->getPosterRealName() . "\n\n";
+		
+
+		$body .= util_line_wrap(util_unconvert_htmlspecialchars($this->getBody())).
+		"\n\n______________________________________________________________________".
+		"\nYou are receiving this email because you elected to monitor this forum.".
+		"\nTo stop monitoring this forum, login to ".$GLOBALS['sys_name']." and visit: ".
+		"\nhttp://".$GLOBALS['sys_default_domain']."/forum/monitor.php?forum_id=".$this->Forum->getID() .'&group_id='.$this->Forum->Group->getID().'&stop=1';
+
+		//$extra_headers = 'Reply-to: '.$this->Forum->getUnixName().'@'.$GLOBALS['sys_default_domain'];
+		$extra_headers = "Return-Path: <noreply at gforge.org>\n";
+		$extra_headers .= "Reply-To: ".$this->Forum->getReturnEmailAddress()."\n";
+		$extra_headers .= "Precedence: Bulk\n"
+			."List-Id: ".$this->Forum->getName()." <forum".$this->Forum->getId()."@".$GLOBALS['sys_default_domain'].">\n"
+			."List-Help: http://".$GLOBALS['sys_default_domain']."/forum/forum.php?id=".$this->Forum->getId()."\n"
+			."Message-Id: <forumpost".$this->getId()."@".$GLOBALS['sys_default_domain'].">";
+		$parentid = $this->getParentId();
+		if (!empty($parentid)) {
+ 			$extra_headers .= "\nIn-Reply-To: ".$this->Forum->getReturnEmailAddress()."\n"
+				."References: <forumpost".$this->getParentId()."@".$GLOBALS['sys_default_domain'].">";
+		}
+
+		$subject="[" . $this->Forum->getUnixName() ."][".$this->getID()."] ".util_unconvert_htmlspecialchars($this->getSubject());
+		if (count($ids) != 0) {
+			$sql="SELECT email FROM users WHERE status='A' AND user_id IN ('".implode($ids,'\',\'')."')";
+			$bccres = db_query($sql);
+		}
+		$BCC =& implode(util_result_column_to_array($bccres),',').','.$this->Forum->getSendAllPostsTo();
+//echo $BCC;
+		$User = user_get_object($this->getPosterID());
+		util_send_message('',$subject,$body,$User->getEmail(),$BCC,$this->getPosterRealName(),$extra_headers);
+//		util_handle_message(array_unique($ids),$subject,$body,$this->Forum->getSendAllPostsTo(),'','forumgateway@'.$GLOBALS[sys_default_domain]);
+		return true;
+	}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/forum/ForumMessageFactory.class
===================================================================
--- trunk/gforge_base/gforge/common/forum/ForumMessageFactory.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/forum/ForumMessageFactory.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,259 @@
+<?php
+/**
+ * GForge Forums Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: ForumMessageFactory.class 1706 2003-02-12 17:23:48Z bigdisk $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+/*
+	Message Forums
+	By Tim Perdue, Sourceforge, 11/99
+
+	Massive rewrite by Tim Perdue 7/2000 (nested/views/save)
+
+	Complete OO rewrite by Tim Perdue 12/2002
+*/
+
+require_once('common/include/Error.class');
+require_once('common/forum/ForumMessage.class');
+
+class ForumMessageFactory extends Error {
+
+	/**
+	 * The Forum object.
+	 *
+	 * @var	 object  $Forum.
+	 */
+	var $Forum;
+
+	/**
+	 * The forum_messages array.
+	 *
+	 * @var  array  forum_messages.
+	 */
+	var $forum_messages;
+	var $style;
+	var $offset;
+	var $max_rows;
+	var $fetched_rows;
+
+	/**
+	 *  Constructor.
+	 *
+	 *	@param	object	The Forum object to which this ForumMessageFactory is associated.
+	 *	@return	boolean	success.
+	 */
+	function ForumMessageFactory(&$Forum) {
+		$this->Error();
+		if (!$Forum || !is_object($Forum)) {
+			$this->setError("ForumMessage:: Invalid group_form_id");
+			return false;
+		}
+		if ($Forum->isError()) {
+			$this->setError('ForumMessage:: '.$Forum->getErrorMessage());
+			return false;
+		}
+		$this->Forum =& $Forum;
+
+		return true;
+	}
+
+	/**
+	 *	setup - call this function before getThreaded/nested/etc to set up the user preferences.
+	 *
+	 *	@param	int	The number of rows to skip.
+	 *	@param	string	The style of forum, whether it's nested, ultimate, etc.
+	 *	@param	int	The maximum number of rows to return.
+	 *	@param	int	Whether to set these prefs into the database - use "custom".
+	 */
+	function setup($offset,$style,$max_rows,$set) {
+//echo "<BR>offset: $offset| style: $style|max_rows: $max_rows|set: $set+";
+		if ((!$offset) || ($offset < 0)) {
+			$this->offset=0;
+		} else {
+			$this->offset=$offset;
+		}
+
+		if (!$style || ($style != 'ultimate' && $style != 'flat' && $style != 'nested' && $style != 'threaded')) {
+			$style='ultimate';
+		}
+		if (!$max_rows || $max_rows < 5) {
+			$max_rows=25;
+		}
+		if (session_loggedin()) {
+			$u =& session_get_user();
+			$_pref=$style.'|'.$max_rows;
+			if ($set=='custom') {
+				if ($u->getPreference('forum_style')) {
+					if ($_pref == $u->getPreference('forum_style')) {
+						//do nothing - pref already stored
+					} else {
+						//set the pref
+						$u->setPreference ('forum_style',$_pref);
+					}
+				} else {
+					//set the pref
+					$u->setPreference ('forum_style',$_pref);
+				}
+			} else {
+				if ($u->getPreference('forum_style')) {
+					$_pref_arr=explode ('|',$u->getPreference('forum_style'));
+					$style=$_pref_arr[0];
+					$max_rows=$_pref_arr[1];
+				} else {
+					//no saved pref and we're not setting
+					//one because this is all default settings
+				}
+			}
+
+		}
+		if (!$style || ($style != 'ultimate' && $style != 'flat' && $style != 'nested' && $style != 'threaded')) {
+			$style='ultimate';
+		}
+		$this->style=$style;
+		if (!$max_rows || $max_rows < 5) {
+			$max_rows=25;
+		}
+		$this->max_rows=$max_rows;
+	}
+
+	/**
+	 *	getStyle - the style of forum this is - nested/ultimate/etc.
+	 *
+	 *	@return	string	The style.
+	 */
+	function getStyle() {
+		return $this->style;
+	}
+
+	/**
+	 *	nestArray - take an array of Forum Messages and building a multi-dimensional associative array for followups.
+	 *
+	 *	@return	array	The nested multi-dimensional associative array.
+	 */
+	function &nestArray(&$row) {
+		$cnt=count($row);
+		for ($i=0; $i<$cnt; $i++) {
+			if ($row[$i]) {
+				$msg_arr["".$row[$i]->getParentID().""][] =& $row[$i];
+			}  
+	  	}
+		return $msg_arr;
+	}
+
+	/**
+	 *	getNested - Return an array of ForumMessage objects arranged for nested forum views.
+	 *
+	 *	@return	array	The array of ForumMessages.
+	 */
+	function &getNested($thread_id=false) {
+		if ($this->forum_messages) {
+			return $this->forum_messages;
+		}
+		if ($thread_id) {
+			$thread_sql=" AND thread_id='$thread_id' ";
+		}
+
+		$sql="SELECT * FROM forum_user_vw 
+		WHERE group_forum_id='".$this->Forum->getID()."' 
+		$thread_sql 
+		ORDER BY most_recent_date DESC";
+
+		$result=db_query($sql,($this->max_rows+25),$this->offset);
+		$rows = db_numrows($result);
+		$this->fetched_rows=$rows;
+		if (!$result || $rows < 1) {
+			$this->setError('No Messages Found '.db_error());
+			return false;
+		} else {
+			while ($arr = db_fetch_array($result)) {
+				$this->forum_messages[] = new ForumMessage($this->Forum, $arr['msg_id'], $arr);
+			}
+		}
+		return $this->forum_messages;
+	}
+
+	/**
+	 *	getThreaded - Return an array of ForumMessage objects arranged for threaded forum views.
+	 *
+	 *	@return	array	The array of ForumMessages.
+	 */
+	function &getThreaded($thread_id=false) {
+		if ($this->forum_messages) {
+			return $this->forum_messages;
+		}
+		if ($thread_id) {
+			$thread_sql=" AND thread_id='$thread_id' ";
+		}
+		$sql="SELECT * FROM forum_user_vw 
+		WHERE group_forum_id='".$this->Forum->getID()."' 
+		$thread_sql 
+		ORDER BY most_recent_date DESC";
+
+		$result=db_query($sql,($this->max_rows+25),$this->offset);
+		$rows = db_numrows($result);
+		$this->fetched_rows=$rows;
+		if (!$result || $rows < 1) {
+			$this->setError('No Messages Found '.db_error());
+			return false;
+		} else {
+			while ($arr = db_fetch_array($result)) {
+				$this->forum_messages[] = new ForumMessage($this->Forum, $arr['msg_id'], $arr);
+			}
+		}
+		return $this->forum_messages;
+	}
+
+	/**
+	 *	getFlat - Return an array of ForumMessage objects arranged for flat forum views.
+	 *
+	 *	@return	array	The array of ForumMessages.
+	 */
+	function &getFlat($thread_id=false) {
+		if ($this->forum_messages) {
+			return $this->forum_messages;
+		}
+		if ($thread_id) {
+			$thread_sql=" AND thread_id='$thread_id' ";
+		}
+		$sql="SELECT * FROM forum_user_vw 
+		WHERE group_forum_id='".$this->Forum->getID()."' 
+		$thread_sql 
+		ORDER BY msg_id DESC";
+
+		$result=db_query($sql,($this->max_rows+1),$this->offset);
+		$rows = db_numrows($result);
+		$this->fetched_rows=$rows;
+		if (!$result || $rows < 1) {
+			$this->setError('No Messages Found '.db_error());
+			return false;
+		} else {
+			while ($arr = db_fetch_array($result)) {
+				$this->forum_messages[] = new ForumMessage($this->Forum, $arr['msg_id'], $arr);
+			}
+		}
+		return $this->forum_messages;
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/forum/ForumsForUser.class
===================================================================
--- trunk/gforge_base/gforge/common/forum/ForumsForUser.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/forum/ForumsForUser.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,89 @@
+<?php
+/**
+ * GForge Forums Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: ForumsForUser.class 4490 2005-07-05 08:52:00Z gsmet $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+require_once('common/include/Error.class');
+require_once('common/forum/Forum.class');
+require_once('common/include/User.class');
+
+class ForumsForUser extends Error {
+
+	/**
+	 * The User object.
+	 *
+	 * @var	 object  $User.
+	 */
+	var $User;
+
+	/**
+	 *  Constructor.
+	 *
+	 *	@param	object	The Group object to which this forum is associated.
+	 */
+	function ForumsForUser(&$user) {
+		$this->User =& $user;
+
+		return true;
+	}
+
+
+	/**
+	*       getMonitoredForums
+	*
+	*       @return Forum[] The array of Forums
+	*
+	*/
+	function getMonitoredForums() {
+		$forums = array();
+		$sql="SELECT groups.group_name,groups.group_id,forum_group_list.group_forum_id,forum_group_list.forum_name ".
+		     "FROM groups,forum_group_list,forum_monitored_forums ".
+		     "WHERE groups.group_id=forum_group_list.group_id AND groups.status ='A' ".
+		     "AND forum_group_list.group_forum_id=forum_monitored_forums.forum_id ".
+		     "AND forum_monitored_forums.user_id='".$this->User->getID()."' ORDER BY group_name DESC";
+
+		$result=db_query($sql);
+		$rows=db_numrows($result);
+		if ($rows < 1) {
+		        return $forums;
+		}
+		$last_group='';
+		for ($i=0; $i<$rows; $i++) {
+			$group_id = db_result($result,$i,'group_id');
+			$forum_id = db_result($result,$i,'group_forum_id');
+			$group =& group_get_object($group_id);
+			$forum =& new Forum($group,$forum_id);
+			if ($forum->isError()) {
+				$this->setError($forum->getErrorMessage());
+			} else {
+				$forums[] =& $forum;
+			}
+		}
+		return $forums;
+	}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/frs/FRSFile.class
===================================================================
--- trunk/gforge_base/gforge/common/frs/FRSFile.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/frs/FRSFile.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,431 @@
+<?php
+/**
+ * GForge File Release Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: FRSFile.class 4883 2005-11-03 19:21:27Z danper $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/include/Error.class');
+
+class FRSFile extends Error {
+
+	/**
+	 * Associative array of data from db.
+	 *
+	 * @var  array   $data_array.
+	 */
+	var $data_array;
+
+	/**
+	 * The FRSRelease.
+	 *
+	 * @var  object  FRSRelease.
+	 */
+	var $FRSRelease;
+
+	/**
+	 *  Constructor.
+	 *
+	 *  @param  object  The FRSRelease object to which this file is associated.
+	 *  @param  int  The file_id.
+	 *  @param  array   The associative array of data.
+	 *	@return	boolean	success.
+	 */
+	function FRSFile(&$FRSRelease, $file_id=false, $arr=false) {
+		$this->Error();
+		if (!$FRSRelease || !is_object($FRSRelease)) {
+			$this->setError('FRSFile:: No Valid FRSRelease Object');
+			return false;
+		}
+		if ($FRSRelease->isError()) {
+			$this->setError('FRSFile:: '.$FRSRelease->getErrorMessage());
+			return false;
+		}
+		$this->FRSRelease =& $FRSRelease;
+
+		if ($file_id) {
+			if (!$arr || !is_array($arr)) {
+				if (!$this->fetchData($file_id)) {
+					return false;
+				}
+			} else {
+				$this->data_array =& $arr;
+				if ($this->data_array['release_id'] != $this->FRSRelease->getID()) {
+					$this->setError('FRSRelease_id in db result does not match FRSRelease Object');
+					$this->data_array=null;
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+
+	/**
+	 *	create - create a new file in this FRSFileRelease/FRSPackage.
+	 *
+	 *	@param	string	The name of this file.
+	 *	@param	string	The location of this file in the local file system.
+	 *	@param	int	The type_id of this file from the frs-file-types table.
+	 *	@param	int	The processor_id of this file from the frs-processor-types table.
+	 *	@param	int	The release_date of this file in unix time (seconds).
+	 *	@return	boolean success.
+	 */
+	function create($name,$file_location,$type_id,$processor_id,$release_time=false) {
+		global $Language;
+		if (strlen($name) < 3) {
+			$this->setError($Language->getText('frs_file','error_min_name_length'));
+			return false;
+		}
+		if (!util_is_valid_filename($name)) {
+			$this->setError($Language->getText('frs_file','error_name_format'));
+			return false;
+		}
+//
+//	Can't really use is_uploaded_file() or move_uploaded_file()
+//	since we want this to be generalized code
+//	This is potentially exploitable if you do not validate 
+//	before calling this function
+//
+		if (!is_file($file_location) || !file_exists($file_location)) {
+			$this->setError($Language->getText('frs_file','error_invalid_file'));
+			return false;
+		}
+
+		$perm =& $this->FRSRelease->FRSPackage->Group->getPermission( session_get_user() );
+
+		if (!$perm || !is_object($perm) || !$perm->isReleaseTechnician()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		//
+		//	Filename must be unique in this release
+		//
+		$resfile=db_query("SELECT filename 
+			FROM frs_file
+			WHERE 
+			filename='$name'
+			AND release_id='".$this->FRSRelease->getId()."'");
+		if (!$resfile || db_numrows($resfile) > 0) {
+			$this->setError($Language->getText('frs_file','error_already_exists').' '.db_error());
+			return false;
+		}
+
+		$path_name=$GLOBALS['sys_upload_dir'].'/'.$this->FRSRelease->FRSPackage->Group->getUnixName();
+		if (!is_dir($path_name)) {
+			mkdir($path_name,0755);
+		} else {
+			if ( fileperms($path_name) != 0x4755 ) {
+				chmod($path_name,0755);
+			}
+		}
+		$path_name=$path_name.'/'.$this->FRSRelease->FRSPackage->getFileName();
+		if (!is_dir($path_name)) {
+			mkdir($path_name,0755);
+		} else {
+			if ( fileperms($path_name) != 0x4755 ) {
+				chmod($path_name,0755);
+			}
+		}
+		$path_name=$path_name.'/'.$this->FRSRelease->getFileName();
+		if (!is_dir($path_name)) {
+			mkdir($path_name,0755);
+		} else {
+			if ( fileperms($path_name) != 0x4755 ) {
+				chmod($path_name,0755);
+			}
+		}
+		
+		$file_location=escapeshellcmd($file_location);
+		$newfilelocation = $GLOBALS['sys_upload_dir'].'/'.
+			$this->FRSRelease->FRSPackage->Group->getUnixName().'/'.
+			$this->FRSRelease->FRSPackage->getFileName().'/'.
+			$this->FRSRelease->getFileName().'/';
+
+		//exec("/bin/mkdir $newfilelocation",$out);
+		//print_r($out);
+		//exec("/bin/mkdir $newfilelocation",$out);
+		//print_r($out);
+		$cmd="/bin/mv $file_location $newfilelocation$name";
+		exec($cmd,$out);
+		//echo $cmd;
+		//print_r($out);
+		if (!file_exists("$newfilelocation$name")) {
+			$this->setError($Language->getText('frs_file','error_cannot_move').': '.$newfilelocation.$name);
+			return false;
+		}
+		if (!$release_time) {
+			$release_time=time();
+		}
+		$file_size=filesize("$newfilelocation$name");
+		$sql="INSERT INTO frs_file(release_id,filename,release_time,
+				type_id,processor_id,file_size,post_date)
+			VALUES ('".$this->FRSRelease->getId()."','$name','$release_time',
+				'$type_id','$processor_id','$file_size','".time()."')";
+
+		db_begin();
+		$result=db_query($sql);
+		if (!$result) {
+			db_rollback();
+			$this->setError('FRSFile::create() Error Adding Release: '.db_error());
+			return false;
+		}
+		$this->file_id=db_insertid($result,'frs_file','file_id');
+		if (!$this->fetchData($this->file_id)) {
+			return false;
+		} else {
+			db_commit();
+			return true;
+		}
+	}
+
+	/**
+	 *  fetchData - re-fetch the data for this FRSFile from the database.
+	 *
+	 *  @param  int  The file_id.
+	 *  @return boolean	success.
+	 */
+	function fetchData($file_id) {
+		$sql="SELECT * FROM frs_file_vw
+			WHERE file_id='$file_id'
+			AND release_id='". $this->FRSRelease->getID() ."'";
+		$res=db_query($sql);
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('FRSFile::fetchData()  Invalid file_id');
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *  getFRSRelease - get the FRSRelease object this file is associated with.
+	 *
+	 *  @return	object	The FRSRelease object.
+	 */
+	function &getFRSRelease() {
+		return $this->FRSRelease;
+	}
+
+	/**
+	 *  getID - get this file_id.
+	 *
+	 *  @return	int	The id of this file.
+	 */
+	function getID() {
+		return $this->data_array['file_id'];
+	}
+
+	/**
+	 *  getName - get the name of this file.
+	 *
+	 *  @return string  The name of this file.
+	 */
+	function getName() {
+		return $this->data_array['filename'];
+	}
+
+	/**
+	 *  getSize - get the size of this file.
+	 *
+	 *  @return int	The size.
+	 */
+	function getSize() {
+		return $this->data_array['size'];
+	}
+
+	/**
+	 *  getTypeID - the filetype id.
+	 *
+	 *  @return int the filetype id.
+	 */
+	function getTypeID() {
+		return $this->data_array['type_id'];
+	}
+
+	/**
+	 *  getTypeName - the filetype name.
+	 *
+	 *  @return string	The filetype name.
+	 */
+	function getFileType() {
+		return $this->data_array['filetype'];
+	}
+
+	/**
+	 *  getProcessorID - the processor id.
+	 *
+	 *  @return int the processor id.
+	 */
+	function getProcessorID() {
+		return $this->data_array['processor_id'];
+	}
+
+	/**
+	 *  getProcessor - the processor name.
+	 *
+	 *  @return string	The processor name.
+	 */
+	function getProcessor() {
+		return $this->data_array['processor'];
+	}
+
+	/**
+	 *  getDownloads - the number of downloads.
+	 *
+	 *  @return int  The number of downloads.
+	 */
+	function getDownloads() {
+		return $this->data_array['downloads'];
+	}
+
+	/**
+	 *  getReleaseTime - get the releasetime of this file.
+	 *
+	 *  @return int	The release time in unix time.
+	 */
+	function getReleaseTime() {
+		return $this->data_array['release_time'];
+	}
+
+	/**
+	 *  getPostDate - get the post time of this file.
+	 *
+	 *  @return int	The post time in unix time.
+	 */
+	function getPostDate() {
+		return $this->data_array['post_time'];
+	}
+
+	/**
+	 *  delete - Delete this file from the database and file system.
+	 *
+	 *  @return	boolean	success.
+	 */
+	function delete() {
+		$perm =& $this->FRSRelease->FRSPackage->Group->getPermission( session_get_user() );
+
+		if (!$perm || !is_object($perm) || !$perm->isReleaseTechnician()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		$file=$GLOBALS['sys_upload_dir'].'/'. 
+			$this->FRSRelease->FRSPackage->Group->getUnixName() . '/' . 
+			$this->FRSRelease->FRSPackage->getFileName().'/'.
+			$this->FRSRelease->getFileName().'/'.
+			$this->getName();
+		unlink($file);
+		$result = db_query("DELETE FROM frs_file WHERE file_id='".$this->getID()."'");
+		if (!$result || db_affected_rows($result) < 1) {
+			$this->setError("frsDeleteFile()::2 ".db_error());
+			return false;
+		} else {
+			$res=db_query("DELETE FROM frs_dlstats_file WHERE file_id='".$this->getID()."'");
+			$res=db_query("DELETE FROM frs_dlstats_filetotal_agg WHERE file_id='".$this->getID()."'");
+			return true;
+		}
+	}
+
+	/**
+	 *	update - update an existing file in this FRSFileRelease/FRSPackage.
+	 *
+	 *	@param	int	The type_id of this file from the frs-file-types table.
+	 *	@param	int	The processor_id of this file from the frs-processor-types table.
+	 *	@param	int	The release_date of this file in unix time (seconds).
+	 *	@param	int	The release_id of the release this file belongs to (if not set, defaults to the release id of this file).
+	 *	@return	boolean success.
+	 */
+	function update($type_id,$processor_id,$release_time,$release_id=false) {
+		global $Language;
+
+		$perm =& $this->FRSRelease->FRSPackage->Group->getPermission( session_get_user() );
+
+		if (!$perm || !is_object($perm) || !$perm->isReleaseTechnician()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		// Sanity checks 
+		if ( $release_id ) {
+			// Check that the new FRSRelease id exists
+			if ($FRSRelease=frsrelease_get_object($release_id)) {
+				// Check that the new FRSRelease id belongs to the group of this FRSFile
+				if ($FRSRelease->FRSPackage->Group->getID()!=$this->FRSRelease->FRSPackage->Group->getID()) {
+					$this->setError('FRSFile:: No Valid Group Object');
+					return false;
+				}
+			} else {
+				$this->setError('FRSFile:: No Valid FRSRelease Object');
+				return false;
+			}
+		} else {
+			// If release_id is not set, defaults to the release id of this file
+			$release_id = $this->FRSRelease->getID();
+		}
+
+		// Update database
+		db_begin();
+		$res=db_query("UPDATE frs_file SET
+			type_id='$type_id',
+			processor_id='$processor_id',
+			release_time='$release_time',
+			release_id='$release_id'
+			WHERE file_id='".$this->getID()."'");
+
+		if (!$res || db_affected_rows($res) < 1) {
+			$this->setError('FRSFile::update() Error On Update: '.db_error());
+			return false;
+		}
+
+		// Move physically file if needed
+		if ($release_id != $this->FRSRelease->getID()) {
+			$old_file_location = $GLOBALS['sys_upload_dir'].'/'.
+				$this->FRSRelease->FRSPackage->Group->getUnixName().'/'.
+				$this->FRSRelease->FRSPackage->getFileName().'/'.
+				$this->FRSRelease->getFileName().'/'.
+				$this->data_array['filename'];
+			$new_file_location = $GLOBALS['sys_upload_dir'].'/'.
+				$FRSRelease->FRSPackage->Group->getUnixName().'/'.
+				$FRSRelease->FRSPackage->getFileName().'/'.
+				$FRSRelease->getFileName().'/'.
+				$this->data_array['filename'];
+			if (file_exists($new_file_location)) {
+				db_rollback();
+				$this->setError($Language->getText('frs_file','error_already_exists'));
+				return false;
+			}
+			$cmd="/bin/mv $old_file_location $new_file_location";
+			exec($cmd,$out);
+			if (!file_exists($new_file_location)) {
+				db_rollback();
+				$this->setError($Language->getText('frs_file','error_cannot_move').': '.$new_file_location);
+				return false;
+			}
+		}
+		db_commit();
+		return true;
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/frs/FRSPackage.class
===================================================================
--- trunk/gforge_base/gforge/common/frs/FRSPackage.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/frs/FRSPackage.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,488 @@
+<?php
+/**
+ * GForge File Release Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: FRSPackage.class 4853 2005-10-31 18:17:58Z lcorso $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/include/Error.class');
+require_once('common/frs/FRSRelease.class');
+
+function &get_frs_packages($Group) {
+	$res=db_query("SELECT * FROM frs_package WHERE group_id='".$Group->getID()."'");
+	if (db_numrows($res) < 1) {
+		return false;
+	}
+	$ps = array();
+	while($arr = db_fetch_array($res)) {
+		$ps[]=new FRSPackage($Group,$arr['package_id'],$arr);
+	}
+	return $ps;
+}
+
+/**
+ * Gets a FRSPackage object from the given package id
+ * 
+ * @param package_id	the package id
+ * @param data	the DB handle if passed in (optional)
+ * @return	the FRSPackage object	
+ */
+function &frspackage_get_object($package_id, $data=false) {
+	global $FRSPACKAGE_OBJ;
+	if (!isset($FRSPACKAGE_OBJ['_'.$package_id.'_'])) {
+		if ($data) {
+			//the db result handle was passed in
+		} else {
+			$res=db_query("SELECT * FROM frs_package
+				WHERE package_id='$package_id'");
+			if (db_numrows($res)<1) {
+				$FRSPACKAGE_OBJ['_'.$package_id.'_']=false;
+				return false;
+			}
+			$data =& db_fetch_array($res);			
+		}
+		$Group =& group_get_object($data['group_id']);
+		$FRSPACKAGE_OBJ['_'.$package_id.'_']= new FRSPackage($Group,$data['package_id'],$data);
+	}
+	return $FRSPACKAGE_OBJ['_'.$package_id.'_'];
+}
+
+class FRSPackage extends Error {
+
+	/**
+	 * Associative array of data from db.
+	 *
+	 * @var  array   $data_array.
+	 */
+	var $data_array;
+	var $package_releases;
+
+	/**
+	 * The Group object.
+	 *
+	 * @var  object  $Group.
+	 */
+	var $Group; //group object
+
+	/**
+	 *  Constructor.
+	 *
+	 *  @param  object  The Group object to which this FRSPackage is associated.
+	 *  @param  int  The package_id.
+	 *  @param  array   The associative array of data.
+	 *	@return	boolean	success.
+	 */
+	function FRSPackage(&$Group, $package_id=false, $arr=false) {
+		$this->Error();
+		if (!$Group || !is_object($Group)) {
+			$this->setError('FRSPackage:: No Valid Group Object');
+			return false;
+		}
+		if ($Group->isError()) {
+			$this->setError('FRSPackage:: '.$Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $Group;
+
+		if ($package_id) {
+			if (!$arr || !is_array($arr)) {
+				if (!$this->fetchData($package_id)) {
+					return false;
+				}
+			} else {
+				$this->data_array =& $arr;
+				if ($this->data_array['group_id'] != $this->Group->getID()) {
+					$this->setError('Group_id in db result does not match Group Object');
+					$this->data_array=null;
+					return false;
+				}
+//
+//	Add an is_public check here
+//
+			}
+		}
+		return true;
+	}
+
+	/**
+	 *	create - create a new FRSPackage in the database.
+	 *
+	 *	@param	string	The name of this package.
+	 *	@param	boolean	Whether it's public or not. 1=public 0=private.
+	 *	@return	boolean success.
+	 */
+	function create($name,$is_public=1) {
+		global $Language,$sys_apache_user,$sys_apache_group;
+		if (strlen($name) < 3) {
+			$this->setError($Language->getText('frs_package','error_min_name_length'));
+			return false;
+		}
+		if (!util_is_valid_filename($name)) {
+			$this->setError($Language->getText('frs_package','error_name_format'));
+		}
+		$perm =& $this->Group->getPermission( session_get_user() );
+
+		if (!$perm || !is_object($perm) || !$perm->isReleaseTechnician()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		$res=db_query("SELECT * FROM frs_package WHERE group_id='".$this->Group->getID()."'
+			AND name='".htmlspecialchars($name)."'");
+		if (db_numrows($res)) {
+			$this->setError('FRSPackage::create() Error Adding Package: Name Already Exists');
+			return false;
+		}
+
+		$sql="INSERT INTO frs_package(group_id,name,status_id,is_public)
+			VALUES ('".$this->Group->getId()."','".htmlspecialchars($name)."','1','$is_public')";
+
+		db_begin();
+		$result=db_query($sql);
+		if (!$result) {
+			db_rollback();
+			$this->setError('FRSPackage::create() Error Adding Package: '.db_error());
+			return false;
+		}
+		$this->package_id=db_insertid($result,'frs_package','package_id');
+		if (!$this->fetchData($this->package_id)) {
+			db_rollback();
+			return false;
+		} else {
+
+			//make groupdir if it doesn't exist
+			$groupdir = $GLOBALS['sys_upload_dir'].'/'.$this->Group->getUnixName();
+			if (!is_dir($groupdir)) {
+				@mkdir($groupdir);
+			}
+
+			$newdirlocation = $GLOBALS['sys_upload_dir'].'/'.$this->Group->getUnixName().'/'.$this->getFileName();
+			exec("/bin/mkdir $newdirlocation",$out);
+			// this 2 should normally silently fail (because it´s called with the apache user) but if it´s root calling the create() method, then the owner and group for the directory should be changed
+			@chown($newdirlocation,$sys_apache_user);
+			@chgrp($newdirlocation,$sys_apache_group);
+			db_commit();
+			return true;
+		}
+	}
+
+	/**
+	 *  fetchData - re-fetch the data for this Package from the database.
+	 *
+	 *  @param  int  The package_id.
+	 *  @return boolean	success.
+	 */
+	function fetchData($package_id) {
+		$res=db_query("SELECT * FROM frs_package
+			WHERE package_id='$package_id'
+			AND group_id='". $this->Group->getID() ."'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('FRSPackage::fetchData()  Invalid package_id'.db_error());
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *  getGroup - get the Group object this FRSPackage is associated with.
+	 *
+	 *  @return	object	The Group object.
+	 */
+	function &getGroup() {
+		return $this->Group;
+	}
+
+	/**
+	 *  getID - get this package_id.
+	 *
+	 *  @return	int	The id of this package.
+	 */
+	function getID() {
+		return $this->data_array['package_id'];
+	}
+
+	/**
+	 *  getName - get the name of this package.
+	 *
+	 *  @return string  The name of this package.
+	 */
+	function getName() {
+		return $this->data_array['name'];
+	}
+
+	/**
+	 *  getFileName - get the filename of this package.
+	 *
+	 *  @return string  The name of this package.
+	 */
+	function getFileName() {
+		return eregi_replace("[^-A-Z0-9_\.]",'',$this->data_array['name']);
+	}
+
+	/**
+	 *  getStatus - get the status of this package.
+	 *
+	 *  @return int	The status.
+	 */
+	function getStatus() {
+		return $this->data_array['status_id'];
+	}
+
+	/**
+	 *	isPublic - whether non-group-members can view.
+	 *
+	 *	@return boolean   is_public.
+	 */
+	function isPublic() {
+		return $this->data_array['is_public'];
+	}
+
+	/**
+	 *  setMonitor - Add the current user to the list of people monitoring this package.
+	 *
+	 *  @return	boolean	success.
+	 */
+	function setMonitor() {
+		global $Language;
+		if (!session_loggedin()) {
+			$this->setError($Language->getText('frs_package','error_set_monitor'));
+			return false;
+		}
+		$sql="SELECT * FROM filemodule_monitor
+			WHERE user_id='".user_getid()."'
+			AND filemodule_id='".$this->getID()."';";
+		$result = db_query($sql);
+
+		if (!$result || db_numrows($result) < 1) {
+			/*
+				User is not already monitoring thread, so
+				insert a row so monitoring can begin
+			*/
+			$sql="INSERT INTO filemodule_monitor (filemodule_id,user_id)
+				VALUES ('".$this->getID()."','".user_getid()."')";
+
+			$result = db_query($sql);
+
+			if (!$result) {
+				$this->setError('Unable to add monitor: '.db_error());
+				return false;
+			}
+
+		}
+		return true;
+	}
+
+	/**
+	 *  stopMonitor - Remove the current user from the list of people monitoring this package.
+	 *
+	 *  @return	boolean	success.
+	 */
+	function stopMonitor() {
+		global $Language;
+		if (!session_loggedin()) {
+			$this->setError($Language->getText('frs_package','error_set_monitor'));
+			return false;
+		}
+		$sql="DELETE FROM filemodule_monitor
+			WHERE user_id='".user_getid()."'
+			AND filemodule_id='".$this->getID()."';";
+		return db_query($sql);
+	}
+
+	/**
+	 *	getMonitorCount - Get the count of people monitoring this package
+	 *
+	 *	@return int the count
+	 */
+	function getMonitorCount() {
+		$sql = "select count(*) as count from filemodule_monitor where filemodule_id = ".$this->getID();
+		$res = db_result(db_query($sql), 0, 0);
+		if ($res < 0) {
+			$this->setError('FRSPackage::getMonitorCount() Error On querying monitor count: '.db_error());
+			return false;
+		}
+		return $res;
+	}	
+
+	/**
+	 *  isMonitoring - Is the current user in the list of people monitoring this package.
+	 *
+	 *  @return	boolean	is_monitoring.
+	 */
+	function isMonitoring() {
+		if (!session_loggedin()) {
+			return false;
+		}
+		$sql="SELECT * FROM filemodule_monitor
+			WHERE user_id='".user_getid()."'
+			AND filemodule_id='".$this->getID()."';";
+
+		$result = db_query($sql);
+
+		if (!$result || db_numrows($result) < 1) {
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 *  getMonitorIDs - Return an array of user_id's of the list of people monitoring this package.
+	 *
+	 *  @return	array	The array of user_id's.
+	 */
+	function &getMonitorIDs() {
+		$res=db_query("SELECT user_id
+			FROM filemodule_monitor
+			WHERE filemodule_id='".$this->getID()."'");
+		return util_result_column_to_array($res);
+	}
+
+	/**
+	 *	update - update an FRSPackage in the database.
+	 *
+	 *	@param	string	The name of this package.
+	 *	@param	int	The status_id of this package from frs_status table.
+	 *	@return	boolean success.
+	 */
+	function update($name,$status) {
+		global $Language;
+		if (strlen($name) < 3) {
+			$this->setError($Language->getText('frs_package','error_min_name_length'));
+			return false;
+		}
+
+		$perm =& $this->Group->getPermission( session_get_user() );
+
+		if (!$perm || !is_object($perm) || !$perm->isReleaseTechnician()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}		
+		if($this->getName()!=htmlspecialchars($name)) {
+			$res=db_query("SELECT * FROM frs_package WHERE group_id='".$this->Group->getID()."'
+			AND name='".htmlspecialchars($name)."'");
+			if (db_numrows($res)) {
+				$this->setError('FRSPackage::update() Error Updating Package: Name Already Exists');
+				return false;
+			}
+		}
+		db_begin();
+		$res=db_query("UPDATE frs_package SET
+			name='".htmlspecialchars($name)."',
+			status_id='$status'
+			WHERE group_id='".$this->Group->getID()."'
+			AND package_id='".$this->getID()."'");
+		if (!$res || db_affected_rows($res) < 1) {
+			db_rollback();
+			$this->setError('FRSPackage::update() Error On Update: '.db_error());
+			return false;
+		}
+
+		$olddirname = $this->getFileName();
+		if(!$this->fetchData($this->getID())){
+			db_rollback();
+			$this->setError('FRSPackage::update() Error Updating Package: Couldn´t fetch data');
+			return false;
+		}
+		$newdirname = $this->getFileName();
+		$olddirlocation = $GLOBALS['sys_upload_dir'].'/'.$this->Group->getUnixName().'/'.$olddirname;
+		$newdirlocation = $GLOBALS['sys_upload_dir'].'/'.$this->Group->getUnixName().'/'.$newdirname;
+		
+		if(($olddirname!=$newdirname)){
+			if(is_dir($newdirlocation)){
+				db_rollback();
+				$this->setError('FRSPackage::update() Error Updating Package: Directory Already Exists');
+				return false;	
+			} else {
+				if(!@rename($olddirlocation,$newdirlocation)) {
+					db_rollback();
+					$this->setError('FRSPackage::update() Error Updating Package: Couldn´t rename dir');
+					return false;
+				}
+			}
+		}	
+		db_commit();
+		return true;
+	}
+
+	/**
+	 *	getReleases - gets Release objects for all the releases in this package.
+	 *
+	 *  return  array   Array of FRSRelease Objects.
+	 */
+	function &getReleases() {
+		if (!is_array($this->package_releases) || count($this->package_releases) < 1) {
+			$this->package_releases=array();
+			$res=db_query("SELECT * FROM frs_release WHERE package_id='".$this->getID()."'");
+			while ($arr = db_fetch_array($res)) {
+				$this->package_releases[]=new FRSRelease($this,$arr['release_id'],$arr);
+			}
+		}
+		return $this->package_releases;
+	}
+
+	/**
+	 *  delete - delete this package and all its related data.
+	 *
+	 *  @param  bool	I'm Sure.
+	 *  @param  bool	I'm REALLY sure.
+	 *  @return   bool true/false;
+	 */
+	function delete($sure, $really_sure) {
+		if (!$sure || !$really_sure) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		$perm =& $this->Group->getPermission( session_get_user() );
+
+		if (!$perm || !is_object($perm) || !$perm->isReleaseTechnician()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		$r =& $this->getReleases();
+		for ($i=0; $i<count($r); $i++) {
+			if (!is_object($r[$i]) || $r[$i]->isError() || !$r[$i]->delete($sure, $really_sure)) {
+				$this->setError('Release Error: '.$r[$i]->getName().':'.$r[$i]->getErrorMessage());
+				return false;
+			}
+		}
+		$dir=$GLOBALS['sys_upload_dir'].'/'.
+			$this->Group->getUnixName() . '/' .
+			$this->getFileName().'/';
+
+		// double-check we're not trying to remove root dir
+		if (util_is_root_dir($dir)) {
+			$this->setError('Package::delete error: trying to delete root dir');
+			return false;
+		}
+		exec('rm -rf '.$dir);
+
+		db_query("DELETE FROM frs_package WHERE package_id='".$this->getID()."'
+			AND group_id='".$this->Group->getID()."'");
+		return true;
+	}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/frs/FRSRelease.class
===================================================================
--- trunk/gforge_base/gforge/common/frs/FRSRelease.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/frs/FRSRelease.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,457 @@
+<?php
+/**
+ * GForge File Release Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: FRSRelease.class 4853 2005-10-31 18:17:58Z lcorso $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+require_once('common/include/Error.class');
+require_once('common/frs/FRSFile.class');
+
+/**
+ *	  Factory method which creates a FRSRelease from an release id
+ *
+ *	  @param int	  The release id
+ *	  @param array	The result array, if it's passed in
+ *	  @return object  FRSRelease object
+ */
+function &frsrelease_get_object($release_id, $data=false) {
+	global $FRSRELEASE_OBJ;
+	if (!isset($FRSRELEASE_OBJ['_'.$release_id.'_'])) {
+		if ($data) {
+					//the db result handle was passed in
+		} else {
+			$res=db_query("SELECT * FROM frs_release WHERE
+			release_id='$release_id'");
+			if (db_numrows($res)<1 ) {
+				$FRSRELEASE_OBJ['_'.$release_id.'_']=false;
+				return false;
+			}
+			$data =& db_fetch_array($res);
+		}
+		$FRSPackage =& frspackage_get_object($data['package_id']);
+		$FRSRELEASE_OBJ['_'.$release_id.'_']= new FRSRelease($FRSPackage,$data['release_id'],$data);
+	}
+	return $FRSRELEASE_OBJ['_'.$release_id.'_'];
+}
+
+class FRSRelease extends Error {
+
+	/**
+	 * Associative array of data from db.
+	 *
+	 * @var  array   $data_array.
+	 */
+	var $data_array;
+
+	/**
+	 * The FRSPackage.
+	 *
+	 * @var  object  FRSPacakge.
+	 */
+	var $FRSPackage;
+	var $release_files;
+
+	/**
+	 *  Constructor.
+	 *
+	 *  @param  object  The FRSPackage object to which this release is associated.
+	 *  @param  int  The release_id.
+	 *  @param  array   The associative array of data.
+	 *	@return	boolean	success.
+	 */
+	function FRSRelease(&$FRSPackage, $release_id=false, $arr=false) {
+		$this->Error();
+		if (!$FRSPackage || !is_object($FRSPackage)) {
+			$this->setError('FRSRelease:: No Valid FRSPackage Object');
+			return false;
+		}
+		if ($FRSPackage->isError()) {
+			$this->setError('FRSRelease:: '.$FRSPackage->getErrorMessage());
+			return false;
+		}
+		$this->FRSPackage =& $FRSPackage;
+
+		if ($release_id) {
+			if (!$arr || !is_array($arr)) {
+				if (!$this->fetchData($release_id)) {
+					return false;
+				}
+			} else {
+				$this->data_array =& $arr;
+				if ($this->data_array['package_id'] != $this->FRSPackage->getID()) {
+					$this->setError('FRSPackage_id in db result does not match FRSPackage Object');
+					$this->data_array=null;
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+
+	/**
+	 *	create - create a new release in the database.
+	 *
+	 *	@param	string	The name of the release.
+	 *	@param	string	The release notes for the release.
+	 *	@param	string	The change log for the release.
+	 *	@param	int	Whether the notes/log are preformatted with \n chars (1) true (0) false.
+	 *	@param	int	The unix date of the release.
+	 *	@return	boolean success.
+	 */
+	function create($name,$notes,$changes,$preformatted,$release_date=false) {
+		global $Language;
+		if (strlen($name) < 3) {
+			$this->setError($Language->getText('frs_release','error_min_name_length'));
+			return false;
+		}
+
+		if ($preformatted) {
+			$preformatted = 1;
+		} else {
+			$preformatted = 0;
+		}
+
+		$perm =& $this->FRSPackage->Group->getPermission( session_get_user() );
+
+		if (!$perm || !is_object($perm) || !$perm->isReleaseTechnician()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		if (!$release_date) {
+			$release_date=time();
+		}
+		$res=db_query("SELECT * FROM frs_release WHERE package_id='".$this->FRSPackage->getID()."'
+			AND name='".htmlspecialchars($name)."'");
+		if (db_numrows($res)) {
+			$this->setError('FRSRelease::create() Error Adding Release: Name Already Exists');
+			return false;
+		}
+
+		$sql="INSERT INTO frs_release(package_id,notes,changes,
+				preformatted,name,release_date,released_by,status_id)
+			VALUES ('".$this->FRSPackage->getId()."','".htmlspecialchars($notes)."','".htmlspecialchars($changes)."',
+				'$preformatted','".htmlspecialchars($name)."','$release_date','".user_getid()."','1')";
+
+		db_begin();
+		$result=db_query($sql);
+		if (!$result) {
+			db_rollback();
+			$this->setError('FRSRelease::create() Error Adding Release: '.db_error());
+			return false;
+		}
+		$this->release_id=db_insertid($result,'frs_release','release_id');
+		if (!$this->fetchData($this->release_id)) {
+			db_rollback();
+			return false;
+		} else {
+			$newdirlocation = $GLOBALS['sys_upload_dir'].'/'.$this->FRSPackage->Group->getUnixName().'/'.$this->FRSPackage->getFileName().'/'.$this->getFileName();
+			exec("/bin/mkdir $newdirlocation",$out);
+			db_commit();
+			return true;
+		}
+	}
+
+	/**
+	 *  fetchData - re-fetch the data for this Release from the database.
+	 *
+	 *  @param  int  The release_id.
+	 *  @return	boolean	success.
+	 */
+	function fetchData($release_id) {
+		$sql="SELECT * FROM frs_release
+			WHERE release_id='$release_id'
+			AND package_id='". $this->FRSPackage->getID() ."'";
+		$res=db_query($sql);
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('FRSRelease::fetchData()  Invalid release_id');
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *  getFRSPackage - get the FRSPackage object this release is associated with.
+	 *
+	 *  @return	object	The FRSPackage object.
+	 */
+	function &getFRSPackage() {
+		return $this->FRSPackage;
+	}
+
+	/**
+	 *  getID - get this release_id.
+	 *
+	 *  @return	int	The id of this release.
+	 */
+	function getID() {
+		return $this->data_array['release_id'];
+	}
+
+	/**
+	 *  getName - get the name of this release.
+	 *
+	 *  @return string  The name of this release.
+	 */
+	function getName() {
+		return $this->data_array['name'];
+	}
+
+	/**
+	 *  getFileName - get the filename of this release.
+	 *
+	 *  @return string  The filename of this release.
+	 */
+	function getFileName() {
+		return eregi_replace("[^-A-Z0-9_\.]",'',$this->data_array['name']);
+	}
+
+	/**
+	 *  getStatus - get the status of this release.
+	 *
+	 *  @return int	The status.
+	 */
+	function getStatus() {
+		return $this->data_array['status_id'];
+	}
+
+	/**
+	 *  getNotes - get the release notes of this release.
+	 *
+	 *  @return string	The release notes.
+	 */
+	function getNotes() {
+		return $this->data_array['notes'];
+	}
+
+	/**
+	 *  getChanges - get the changelog of this release.
+	 *
+	 *  @return string	The changelog.
+	 */
+	function getChanges() {
+		return $this->data_array['changes'];
+	}
+
+	/**
+	 *  getPreformatted - get the preformatted option of this release.
+	 *
+	 *  @return	boolean	preserve_formatting.
+	 */
+	function getPreformatted() {
+		return $this->data_array['preformatted'];
+	}
+
+	/**
+	 *  getReleaseDate - get the releasedate of this release.
+	 *
+	 *  @return int	The release date in unix time.
+	 */
+	function getReleaseDate() {
+		return $this->data_array['release_date'];
+	}
+
+	/**
+	 *  sendNotice - the logic to send an email/jabber notice for a release.
+	 *
+	 *  @return	boolean	success.
+	 */
+	function sendNotice() {
+		global $Language;
+		$arr =& $this->FRSPackage->getMonitorIDs();
+
+		$date = date('Y-m-d H:i',time());
+		$proto = "http://";
+		if ($GLOBALS['sys_use_ssl']) {
+			$proto = "https://";
+		}
+
+		$subject = $Language->getText('frs_release','email_title',array(
+		$this->FRSPackage->Group->getUnixName(),
+		$this->FRSPackage->getName()));
+		$text = stripcslashes($Language->getText('frs_release','email_text',array(
+		$this->FRSPackage->Group->getPublicName(),
+		$this->FRSPackage->Group->getUnixName(),
+		$this->FRSPackage->getName(),
+		"<${proto}".getStringFromServer('HTTP_HOST')."/project/showfiles.php?group_id=". $this->FRSPackage->Group->getID() ."&release_id=". $this->getID().">",
+		$GLOBALS['sys_name'],
+		"<${proto}".getStringFromServer('HTTP_HOST')."/frs/monitor.php?filemodule_id=".$this->FRSPackage->getID()."&group_id=".$this->FRSPackage->Group->getID()."&stop=1>")));
+			
+
+		$text = util_line_wrap($text);
+		if (count($arr)) {
+			util_handle_message(array_unique($arr),$subject,$text);
+		}
+		
+	}
+
+	/**
+	 *	getFiles - gets all the file objects for files in this release.
+	 *
+	 *	return	array	Array of FRSFile Objects.
+	 */
+	function &getFiles() {
+		if (!is_array($this->release_files) || count($this->release_files) < 1) {
+			$this->release_files=array();
+			$res=db_query("SELECT * FROM frs_file_vw WHERE release_id='".$this->getID()."'");
+			while ($arr = db_fetch_array($res)) {
+				$this->release_files[]=new FRSFile($this,$arr['file_id'],$arr);
+			}
+		}
+		return $this->release_files;
+	}
+
+	/**
+	 *  delete - delete this release and all its related data.
+	 *
+	 *  @param  bool	I'm Sure.
+	 *  @param  bool	I'm REALLY sure.
+	 *  @return   bool true/false;
+	 */
+	function delete($sure, $really_sure) {
+		if (!$sure || !$really_sure) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		$perm =& $this->FRSPackage->Group->getPermission( session_get_user() );
+
+		if (!$perm || !is_object($perm) || !$perm->isReleaseTechnician()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		$f =& $this->getFiles();
+		for ($i=0; $i<count($f); $i++) {
+			if (!is_object($f[$i]) || $f[$i]->isError() || !$f[$i]->delete()) {
+				$this->setError('File Error: '.$f[$i]->getName().':'.$f[$i]->getErrorMessage());
+				return false;
+			}
+		}
+		$dir=$GLOBALS['sys_upload_dir'].'/'.
+			$this->FRSPackage->Group->getUnixName() . '/' .
+			$this->FRSPackage->getFileName().'/'.
+			$this->getFileName().'/';
+		
+		// double-check we're not trying to remove root dir
+		if (util_is_root_dir($dir)) {
+			$this->setError('Release::delete error: trying to delete root dir');
+			return false;
+		}
+		exec('rm -rf '.$dir);
+		
+		db_query("DELETE FROM frs_release WHERE release_id='".$this->getID()."'
+			AND package_id='".$this->FRSPackage->getID()."'");
+		return true;
+	}
+
+	/**
+	 *	create - create a new release in the database.
+	 *
+	 *	@param	int	The status of this release from the frs_status table.
+	 *	@param	string	The name of the release.
+	 *	@param	string	The release notes for the release.
+	 *	@param	string	The change log for the release.
+	 *	@param	int	Whether the notes/log are preformatted with \n chars (1) true (0) false.
+	 *	@param	int	The unix date of the release.
+	 *	@return	boolean success.
+	 */
+	function update($status,$name,$notes,$changes,$preformatted,$release_date) {
+
+		global $Language;
+		if (strlen($name) < 3) {
+			$this->setError($Language->getText('frs_release','error_min_name_length'));
+			return false;
+		}
+		
+		$perm =& $this->FRSPackage->Group->getPermission( session_get_user() );
+
+		if (!$perm || !is_object($perm) || !$perm->isReleaseTechnician()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		if ($preformatted) {
+			$preformatted = 1;
+		} else {
+			$preformatted = 0;
+		}
+		
+		if($this->getName()!=htmlspecialchars($name)) {
+			$res=db_query("SELECT * FROM frs_release WHERE package_id='".$this->FRSPackage->getID()."'
+			AND name='".htmlspecialchars($name)."'");
+			if (db_numrows($res)) {
+				$this->setError('FRSRelease::create() Error Adding Release: Name Already Exists');
+				return false;
+			}
+		}		
+		db_begin();
+		$res=db_query("UPDATE frs_release
+			SET
+			name='".htmlspecialchars($name)."',
+			status_id='$status',
+			notes='".htmlspecialchars($notes)."',
+			changes='".htmlspecialchars($changes)."',
+			preformatted='$preformatted',
+			release_date='$release_date',
+			released_by='". user_getid() ."'
+			WHERE package_id='".$this->FRSPackage->getID()."'
+			AND release_id='".$this->getID()."'");
+
+		if (!$res || db_affected_rows($res) < 1) {
+			db_rollback();
+			$this->setError('FRSRelease::update() Error On Update: '.db_error());
+			return false;
+		}
+
+		$oldfilename = $this->getFileName();
+		if(!$this->fetchData($this->getID())){
+			db_rollback();
+			$this->setError('FRSRelease::update() Error Updating Release: Couldn´t fetch data');
+			return false;
+		}
+		$newfilename = $this->getFileName();
+		$olddirlocation = $GLOBALS['sys_upload_dir'].'/'.$this->FRSPackage->Group->getUnixName().'/'.$this->FRSPackage->getFileName().'/'.$oldfilename;
+		$newdirlocation = $GLOBALS['sys_upload_dir'].'/'.$this->FRSPackage->Group->getUnixName().'/'.$this->FRSPackage->getFileName().'/'.$newfilename;
+	
+		if(($oldfilename!=$newfilename)){
+			if(is_dir($newdirlocation)){
+				db_rollback();
+				$this->setError('FRSRelease::update() Error Updating Release: Directory Already Exists');
+				return false;	
+			} else {
+				if(!rename($olddirlocation,$newdirlocation)) {
+					db_rollback();
+					$this->setError('FRSRelease::update() Error Updating Release: Couldn´t rename dir');
+					return false;
+				}
+			}
+		}	
+		db_commit();
+		return true;
+	}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/include/Error.class
===================================================================
--- trunk/gforge_base/gforge/common/include/Error.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/Error.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,221 @@
+<?php   
+/**
+ * A very base error class.
+ *
+ * Provides a basic uniform API for setting and testing error conditions and
+ * error messages.
+ *
+ * @version   $Id: Error.class 3315 2004-09-12 17:42:26Z gsmet $
+ * @author Tim Perdue <tperdue at valnux.com>
+ * @date 2000-08-28
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+define('ERROR__NO_ERROR', 0);
+define('ERROR__UNCLASSIFIED_ERROR', 1);
+define('ERROR__PERMISSION_DENIED_ERROR', 2);
+define('ERROR__INVALID_EMAIL_ERROR', 3);
+define('ERROR__ON_UPDATE_ERROR', 4);
+define('ERROR__GROUPID_ERROR', 5);
+define('ERROR__MISSING_PARAMS_ERROR', 6);
+
+class Error {
+	/**
+	 * The current error state.
+	 *
+	 * @var bool $error_state.
+	 */
+	var $error_state;
+
+	/**
+	 * The current error message(s).
+	 *
+	 * @var string $error_message.
+	 */
+	var $error_message;
+
+	/**
+	 * The current error code
+	 *
+	 * @var int $error_code.
+	 */
+	var $error_code;
+	
+	/**
+	 * Error() - Constructor.
+	 * Constructor for the Error class.
+	 * Sets the error state to false.
+	 *
+	 */
+	function Error() {
+		//nothing
+		$this->error_state=false;
+		$this->error_code=ERROR__NO_ERROR;
+	}
+
+	/**
+	 * setError() - Sets the error string.
+	 * Set the error string $error_message to the value of $string
+	 * and enable the $error_state flag.
+	 *
+	 * @param	string  The error string to set.
+	 * @param	int	The error code
+	 */
+	function setError($string, $code=ERROR__UNCLASSIFIED_ERROR) {
+		$this->error_state=true;
+		$this->error_message=$string;
+		$this->error_code=$code;
+	}
+
+	/**
+	 * clearError() - Clear the current error.
+	 * Clear the current error string and disable the $error_state flag.
+	 *
+	 */
+	function clearError() {
+		$this->error_state=false;
+		$this->error_code=ERROR__NO_ERROR;
+		$this->error_message='';
+	}
+
+	/**
+	 * getErrorMessage() - Retrieve the error message string.
+	 * Returns the value of $error_message.
+	 *
+	 * @return    $error_message The current error message string.
+	 *
+	 */
+	function getErrorMessage() {
+		if ($this->error_state)	{
+			return $this->error_message;
+		} else {
+			return 'No Error';
+		}
+	}
+
+	/**
+	 * isError() - Determines the current error state.
+	 * This function returns the current value of $error_state.
+	 *
+	 * @return    $error_state     The boolean error status.
+	 *
+	 */
+	function isError() {
+		return $this->error_state;
+	}
+	
+
+	/**
+	 * setPermissionDeniedError() - sets a Permission Denied error
+	 *  retrieves the localized error string for Permission Denied and calls exit_error()
+	 *
+	 *
+	 */
+	function setPermissionDeniedError(){
+		global $Language;
+		$this->setError($Language->getText('general','permdenied'), ERROR__PERMISSION_DENIED_ERROR);
+	}
+	
+	/**
+	 * isPermissionDeniedError() - Determines if it is a permission denied error
+	 *
+	 * @return	boolean
+	 */
+	function isPermissionDeniedError(){
+		return ($this->error_code == ERROR__PERMISSION_DENIED_ERROR);
+	}
+
+	/**
+	 * setInvalidEmailError() - sets a Invalid Email error
+	 *  retrieves the localized error string for Invalid Email and calls exit_error()
+	 */
+	function setInvalidEmailError(){
+		global $Language;
+		$this->setError($Language->getText('general','invalid_email'), ERROR__INVALID_EMAIL_ERROR);
+	}
+	
+	/**
+	 * isInvalidEmailError() - Determines if it is an invalid email error
+	 *
+	 * @return	boolean
+	 */
+	function isInvalidEmailError(){
+		return ($this->error_code == ERROR__INVALID_EMAIL_ERROR);
+	}
+	
+	/**
+	 * setOnUpdateError() - sets an On Update Error
+	 *  retrieves the localized error string for On Update
+	 *
+	 * @param	string  The db result to be written.
+	 *
+	 */
+	function setOnUpdateError($result=""){
+		global $Language;
+		$this->setError($Language->getText('general','error_on_update',$result), ERROR__ON_UPDATE_ERROR);	
+	}
+	
+	/**
+	 * isOnUpdateError() - Determines if it is an on update error
+	 *
+	 * @return	boolean
+	 */
+	function isOnUpdateError(){
+		return ($this->error_code == ERROR__ON_UPDATE_ERROR);
+	}
+
+	/**
+	 * setGroupIdError() - sets an Group ID Error
+	 *  retrieves the localized error string for Group ID 
+	 */
+	function setGroupIdError(){
+		global $Language;
+		$this->setError($Language->getText('general','error_group_id'), ERROR__GROUPID_ERROR);
+		
+	}
+	
+	/**
+	 * isGroupIdError() - Determines if it is a group ID error
+	 *
+	 * @return	boolean
+	 */
+	function isGroupIdError(){
+		return ($this->error_code == ERROR__GROUPID_ERROR);
+	}
+
+	/**
+	 * setMissingParamsError() - sets an Group ID Error
+	 *  retrieves the localized error string for missing pparams
+	 */
+	function setMissingParamsError(){
+		global $Language;
+		$this->setError($Language->getText('general','error_missing_params'), ERROR__MISSING_PARAMS_ERROR);
+	}
+	
+	/**
+	 * isMissingParamsError() - Determines if it is a missing params error
+	 *
+	 * @return	boolean
+	 */
+	function isMissingParamsError(){
+		return ($this->error_code == ERROR__MISSING_PARAMS_ERROR);
+	}
+
+}
+
+?>
\ No newline at end of file

Added: trunk/gforge_base/gforge/common/include/GForge.class
===================================================================
--- trunk/gforge_base/gforge/common/include/GForge.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/GForge.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,80 @@
+<?php   
+/**
+ *	GForge object
+ *
+ *	Provides some top-level information about the GForge installation.
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+class GForge extends Error {
+
+	/**
+	 *	GForge - GForge object constructor
+	 */
+	function GForge() {
+		$this->Error();
+		return true;
+	}
+
+	function getNumberOfHostedProjects() {
+		$res=db_query("SELECT count(*) AS count FROM groups WHERE status='A' AND is_public=1");	
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('Unable to get hosted project count: '.db_error());
+			return false;
+		}
+		return $this->parseCount($res);
+	}
+
+	function getNumberOfActiveUsers() {
+	  $res = db_query("SELECT count(*) AS count FROM users WHERE status='A' and user_id != 100");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('Unable to get user count: '.db_error());
+			return false;
+		}
+		return $this->parseCount($res);
+	}
+
+
+	function getPublicProjectNames() {
+		$res = db_query("SELECT unix_group_name FROM groups WHERE status='A' AND is_public=1");
+		if (!$res) {
+			$this->setError('Unable to get list of public projects: '.db_error());
+			return false;
+		}
+		$rows=db_numrows($res);
+		$result = array();
+    for ($i=0; $i<$rows; $i++) {
+			$result[$i] = db_result($res, $i, 'unix_group_name');
+    }
+		return $result;
+	}
+	
+	function parseCount($res) {
+    $row_count = db_fetch_array($res);
+    return $row_count['count'];
+	}
+
+
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/include/Group.class
===================================================================
--- trunk/gforge_base/gforge/common/include/Group.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/Group.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,2294 @@
+<?php   
+/**
+ *	Group object
+ *
+ *	Sets up database results and preferences for a group and abstracts this info.
+ *
+ *	Foundry.class and Project.class call this.
+ *
+ *	Project.class contains all the deprecated API from the old group.php file
+ *
+ *	DEPENDS on user.php being present and setup properly
+ *
+ *	GENERALLY YOU SHOULD NEVER INSTANTIATE THIS OBJECT DIRECTLY
+ *	USE group_get_object() to instantiate properly
+ *
+ * @version   $Id: Group.class 4708 2005-10-04 11:55:12Z danper $
+ * @author Tim Perdue <tperdue at valinux.com>
+ * @date 2000-08-28
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/tracker/ArtifactTypes.class');
+require_once('common/tracker/ArtifactTypeFactory.class');
+require_once('common/forum/Forum.class');
+require_once('common/forum/ForumFactory.class');
+require_once('common/pm/ProjectGroup.class');
+require_once('common/pm/ProjectGroupFactory.class');
+require_once('common/include/Role.class');
+require_once('common/frs/FRSPackage.class');
+require_once('common/docman/DocumentGroup.class');
+require_once('common/mail/MailingList.class');
+require_once('common/mail/MailingListFactory.class');
+require_once('www/include/BaseLanguage.class');
+
+//the license_id of "Other/proprietary" license
+define('GROUP_LICENSE_OTHER',126);
+
+$LICENSE_NAMES=array();
+
+/**
+ * group_get_licences() - get the licenses list
+ *
+ * @return array list of licenses
+ */
+function & group_get_licenses() {
+	global $LICENSE_NAMES;
+	if(empty($LICENSE_NAMES)) {
+		$result = db_query('select * from licenses');
+		while($data = db_fetch_array($result)) {
+			$LICENSE_NAMES[$data['license_id']] = $data['license_name'];
+		}
+	}
+	return $LICENSE_NAMES;
+}
+
+$GROUP_OBJ=array();
+
+/**
+ *  group_get_object() - Get the group object.
+ *
+ *  group_get_object() is useful so you can pool group objects/save database queries
+ *  You should always use this instead of instantiating the object directly.
+ *
+ *  You can now optionally pass in a db result handle. If you do, it re-uses that query
+ *  to instantiate the objects.
+ *
+ *  IMPORTANT! That db result must contain all fields
+ *  from groups table or you will have problems
+ *
+ *  @param		int		Required
+ *  @param		int		Result set handle ("SELECT * FROM groups WHERE group_id=xx")
+ *  @return a group object or false on failure
+ */
+function &group_get_object($group_id,$res=false) {
+	//create a common set of group objects
+	//saves a little wear on the database
+
+	//automatically checks group_type and 
+	//returns appropriate object
+	
+	global $GROUP_OBJ;
+	if (!isset($GROUP_OBJ["_".$group_id."_"])) {
+		if ($res) {
+			//the db result handle was passed in
+		} else {
+			$res=db_query("SELECT * FROM groups WHERE group_id='$group_id'");
+		}
+		if (!$res || db_numrows($res) < 1) {
+			$GROUP_OBJ["_".$group_id."_"]=false;
+		} else {
+			/*
+				check group type and set up object
+			*/
+			if (db_result($res,0,'type_id')==1) {
+				//project
+				$GROUP_OBJ["_".$group_id."_"]= new Group($group_id,$res);
+			} else {
+				//invalid
+				$GROUP_OBJ["_".$group_id."_"]=false;
+			}
+		}
+	}
+	return $GROUP_OBJ["_".$group_id."_"];
+}
+
+function &group_get_objects($id_arr) {
+	global $GROUP_OBJ;
+	
+	// Note: if we don't do this, the result may be corrupted
+	$fetch = array();
+	$return = array();
+	
+	for ($i=0; $i<count($id_arr); $i++) {
+		//
+		//	See if this ID already has been fetched in the cache
+		//
+		if (!$id_arr[$i]) {
+			continue;
+		}
+		if (!isset($GROUP_OBJ["_".$id_arr[$i]."_"])) {
+			$fetch[]=$id_arr[$i];
+		} else {
+			$return[] =& $GROUP_OBJ["_".$id_arr[$i]."_"];
+		}
+	}
+	if (count($fetch) > 0) {
+		$res=db_query("SELECT * FROM groups WHERE group_id IN ('".implode($fetch,'\',\'') ."')");
+		while ($arr =& db_fetch_array($res)) {
+			$GROUP_OBJ["_".$arr['group_id']."_"] = new Group($arr['group_id'],$arr);
+			$return[] =& $GROUP_OBJ["_".$arr['group_id']."_"];
+		}
+	}
+	return $return;
+}
+
+function &group_get_object_by_name($groupname) {
+	$res=db_query("SELECT * FROM groups WHERE unix_group_name='$groupname'");
+	return group_get_object(db_result($res,0,'group_id'),$res);
+}
+
+function &group_get_objects_by_name($groupname_arr) {
+	$sql="SELECT group_id FROM groups WHERE unix_group_name IN ('".implode($groupname_arr,'\',\'')."')";
+	$res=db_query($sql);
+	$arr =& util_result_column_to_array($res,0);
+	return group_get_objects($arr);
+}
+
+class Group extends Error {
+	/**
+	 * Associative array of data from db.
+	 * 
+	 * @var array $data_array.
+	 */
+	var $data_array;
+
+	/**
+	 * array of User objects.
+	 * 
+	 * @var array $membersArr.
+	 */
+	var $membersArr;
+
+	/**
+	 * Permissions data row from db.
+	 * 
+	 * @var array $perm_data_array.
+	 */
+	var $perm_data_array;
+
+	/**
+	 * Whether the use is an admin/super user of this project.
+	 *
+	 * @var bool $is_admin.
+	 */
+	var $is_admin;
+
+	/**
+	 * Artifact types result handle.
+	 * 
+	 * @var int $types_res.
+	 */
+	var $types_res;
+
+	/**
+	 * Associative array of data for plugins.
+	 * 
+	 * @var array $plugins_array.
+	 */
+	var $plugins_array;
+
+	/**
+	 *	Group - Group object constructor - use group_get_object() to instantiate.
+	 *
+	 *	@param	int		Required - group_id of the group you want to instantiate.
+	 *	@param	int		Database result from select query OR associative array of all columns.
+	 */
+	function Group($id=false, $res=false) {
+		$this->Error();
+		if (!$id) {
+			//setting up an empty object
+			//probably going to call create()
+			return true;
+		}
+		if (!$res) {
+			if (!$this->fetchData($id)) {
+				return false;
+			}
+		} else {
+			//
+			//	Assoc array was passed in
+			//
+			if (is_array($res)) {
+				$this->data_array =& $res;
+			} else {
+				if (db_numrows($res) < 1) {
+					//function in class we extended
+					$this->setError('Group Not Found');
+					$this->data_array=array();
+					return false;
+				} else {
+					//set up an associative array for use by other functions
+					db_reset_result($res);
+					$this->data_array =& db_fetch_array($res);
+				}
+			}
+		}
+		
+		$systemGroups = array(GROUP_IS_NEWS, GROUP_IS_STATS, GROUP_IS_PEER_RATINGS);
+		if(!$this->isPublic() && !in_array($id, $systemGroups)) {
+			$perm =& $this->getPermission(session_get_user());
+
+			if (!$perm || !is_object($perm) || !$perm->isMember()) {
+				// cannot use $Language as it is not created yet
+				$this->setError('Permission denied', ERROR__PERMISSION_DENIED_ERROR);
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 *	fetchData - May need to refresh database fields if an update occurred.
+	 *
+	 *	@param	int	The group_id.
+	 */
+	function fetchData($group_id) {
+		$res = db_query("SELECT * FROM groups WHERE group_id='$group_id'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('fetchData():: '.db_error());
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		return true;
+	}
+
+	/**
+	 *	create - Create new group.
+	 *
+	 *	This method should be called on empty Group object.
+	 *  
+	 *  @param	object	The User object.
+	 *  @param	string	The full name of the user.
+	 *  @param	string	The Unix name of the user.
+	 *  @param	string	The new group description.
+	 *  @param	int	The ID of the license to use.
+	 *  @param	string	The 'other' license to use if any.
+	 *  @param	string	The purpose of the group.
+	 */
+	function create(&$user, $full_name, $unix_name, $description, $license, $license_other, $purpose, $unix_box='shell1', $scm_box='cvs1') {
+		global $Language;
+
+		// $user is ignored - anyone can create pending group
+
+		if ($this->getID()!=0) {
+			$this->setError("Group::create: Group object already exists");
+			return false;
+		} else if (strlen($full_name)<3) {
+			$this->setError($Language->getText('register','invalid_full_name'));
+			return false;
+		} else if (!account_groupnamevalid($unix_name)) {
+			$this->setError($Language->getText('register','invalid_unix_name'));
+			return false;
+		} else if (db_numrows(db_query("SELECT group_id FROM groups WHERE unix_group_name='$unix_name'")) > 0) {
+			$this->setError($Language->getText('register','unix_group_name_already_taken'));
+			return false;
+		} else if (strlen($purpose)<10) {
+			$this->setError($Language->getText('register','describe_registration'));
+			return false;
+		} else if (strlen($purpose)>1500) {
+			$this->setError($Language->getText('register','describe_registration'));
+			return false;
+		} else if (strlen($description)<10) {
+			$this->setError($Language->getText('register','comprehensive_description'));
+			return false;
+		} else if (!$license) {
+			$this->setError($Language->getText('register','no_license_chosen'));
+			return false;
+		} else if ($license!=GROUP_LICENSE_OTHER && $license_other) {
+			$this->setError($Language->getText('register','conflicting_licenses_choice'));
+			return false;
+		} else if ($license==GROUP_LICENSE_OTHER && strlen($license_other)<50) {
+			$this->setError($Language->getText('register','more_license_description'));
+			return false;
+		} else {
+
+			srand((double)microtime()*1000000);
+			$random_num = rand(0,1000000);
+	
+			db_begin();
+	
+			$res = db_query("
+				INSERT INTO groups (
+					group_name,
+					is_public,
+					unix_group_name,
+					short_description,
+					http_domain,
+					homepage,
+					status,
+					unix_box,
+					scm_box,
+					license,
+					register_purpose,
+					register_time,
+					license_other,
+					rand_hash
+				)
+				VALUES (
+					'".htmlspecialchars($full_name)."',
+					1,
+					'$unix_name',
+					'".htmlspecialchars($description)."',
+					'$unix_name.".$GLOBALS['sys_default_domain']."',
+					'$unix_name.".$GLOBALS['sys_default_domain']."',
+					'P',
+					'$unix_box',
+					'$scm_box',
+					'$license',
+					'".htmlspecialchars($purpose)."',
+					".time().",
+					'".htmlspecialchars($license_other)."',
+					'".md5($random_num)."'
+				)
+			");
+	
+			if (!$res || db_affected_rows($res) < 1) {
+				$this->setError('ERROR: Could not create group: '.db_error());
+				db_rollback();
+				return false;
+			}
+	
+			$id = db_insertid($res, 'groups', 'group_id');
+			if (!$id) {
+				$this->setError('ERROR: Could not get group id: '.db_error());
+				db_rollback();
+				return false;
+			}
+	
+			//
+			// Now, make the user an admin
+			//
+			$sql="INSERT INTO user_group ( user_id, group_id, admin_flags,
+				cvs_flags, artifact_flags, forum_flags, role_id)
+				VALUES ( ".$user->getID().", '$id', 'A', 1, 2, 2, 1)";
+	
+			$res=db_query($sql);
+			if (!$res || db_affected_rows($res) < 1) {
+				$this->setError('ERROR: Could not add admin to newly created group: '.db_error());
+				db_rollback();
+				return false;
+			}
+	
+			if (!$this->fetchData($id)) {
+				db_rollback();
+				return false;
+			}
+			db_commit();
+			$this->sendNewProjectNotificationEmail();
+			return true;
+		}
+	}
+
+
+	/**
+	 *	updateAdmin - Update core properties of group object.
+	 *
+	 *	This function require site admin privilege.
+	 *
+	 *	@param	object	User requesting operation (for access control).
+	 *	@param	bool	Whether group is publicly accessible (0/1).
+	 *	@param	string	Project's license (string ident).
+	 *	@param	int		Group type (1-project, 2-foundry).
+	 *	@param	string	Machine on which group's home directory located.
+	 *	@param	string	Domain which serves group's WWW.
+	 *	@return status.
+	 *	@access public.
+	 */
+	function updateAdmin(&$user, $is_public, $license, $type_id, $unix_box, $http_domain) {
+		global $Language;
+
+		$perm =& $this->getPermission($user);
+
+		if (!$perm || !is_object($perm)) {
+			$this->setError($Language->getText('general','permnotget'));
+			return false;
+		}
+
+		if (!$perm->isSuperUser()) {
+			$this->setError($Language->getText('general','permdenied'));
+			return false;
+		}
+
+		db_begin();
+
+		$res = db_query("
+			UPDATE groups
+			SET is_public='$is_public',
+				license='$license',type_id='$type_id',
+				unix_box='$unix_box',http_domain='$http_domain'
+			WHERE group_id='".$this->getID()."'
+		");
+
+		if (!$res || db_affected_rows($res) < 1) {
+			$this->setError('ERROR: DB: Could not change group properties: '.db_error());
+			db_rollback();
+			return false;
+		}
+
+		// Log the audit trail
+		if ($is_public != $this->isPublic()) {
+			$this->addHistory('is_public', $this->isPublic());
+		}
+		if ($license != $this->data_array['license']) {
+			$this->addHistory('license', $this->data_array['license']);
+		}
+		if ($type_id != $this->data_array['type_id']) {
+			$this->addHistory('type_id', $this->data_array['type_id']);
+		}
+		if ($unix_box != $this->data_array['unix_box']) {
+			$this->addHistory('unix_box', $this->data_array['unix_box']);
+		}
+		if ($http_domain != $this->data_array['http_domain']) {
+			$this->addHistory('http_domain', $this->data_array['http_domain']);
+		}
+
+		if (!$this->fetchData($this->getID())) {
+			db_rollback();
+			return false;
+		}
+		db_commit();
+		return true;
+	}
+
+	/**
+	 *	update - Update number of common properties.
+	 *
+	 *	Unlike updateAdmin(), this function accessible to project admin.
+	 *
+	 *	@param	object	User requesting operation (for access control).
+	 *	@param	bool	Whether group is publicly accessible (0/1).
+	 *	@param	string	Project's license (string ident).
+	 *	@param	int		Group type (1-project, 2-foundry).
+	 *	@param	string	Machine on which group's home directory located.
+	 *	@param	string	Domain which serves group's WWW.
+	 *	@return int	status.
+	 *	@access public.
+	 */
+	function update(&$user, $group_name,$homepage,$short_description,$use_mail,$use_survey,$use_forum,
+		$use_pm,$use_pm_depend_box,$use_scm,$use_news,$use_docman,
+		$new_doc_address,$send_all_docs,$logo_image_id,
+		$enable_pserver,$enable_anonscm,
+		$use_ftp,$use_tracker,$use_frs,$use_stats) {
+		global $Language;
+
+		$perm =& $this->getPermission($user);
+
+		if (!$perm || !is_object($perm)) {
+			$this->setError($Language->getText('general','permnotget'));
+			return false;
+		}
+
+		if (!$perm->isAdmin()) {
+			$this->setError($Language->getText('general','permdenied'));
+			return false;
+		}
+
+		// Validate some values
+		if (!$group_name) {
+			$this->setError('Invalid Group Name');
+			return false;
+		}
+
+		if ($new_doc_address) {
+			$invalid_mails = validate_emails($new_doc_address);
+			if (count($invalid_mails) > 0) {
+				$this->setError('New Doc Address(es) Appeared Invalid: '.implode(',',$invalid_mails));
+				return false;
+			}
+		}
+
+		// in the database, these all default to '1',
+		// so we have to explicity set 0
+		if (!$use_mail) {
+			$use_mail=0;
+		}
+		if (!$use_forum) {
+			$use_forum=0;
+		}
+		if (!$use_pm) {
+			$use_pm=0;
+		}
+		if (!$use_pm_depend) {
+			$use_pm_depend=0;
+		}
+		if (!$use_scm) {
+			$use_scm=0;
+		}
+		if (!$use_news) {
+			$use_news=0;
+		}
+		if (!$use_docman) {
+			$use_docman=0;
+		}
+		if (!$send_all_tasks) {
+			$send_all_tasks=0;
+		}
+		if (!$use_ftp) {
+			$use_ftp=0;
+		}
+		if (!$use_tracker) {
+			$use_tracker=0;
+		}
+		if (!$use_frs) {
+			$use_frs=0;
+		}
+		if (!$use_stats) {
+			$use_stats=0;
+		}
+		if (!$send_all_docs) {
+			$send_all_docs=0;
+		}
+
+		if (!$homepage) {
+			$homepage=$GLOBALS['sys_default_domain'].'/projects/'.$this->getUnixName().'/';
+		}
+
+		if (strlen($short_description)>255) {
+			$this->setError('Error updating project information: Maximum length for Project Description is 255 chars.');
+			return false;
+		}
+
+		db_begin();
+
+		//XXX not yet actived logo_image_id='$logo_image_id', 
+		$sql = "
+			UPDATE groups
+			SET 
+				group_name='".htmlspecialchars($group_name)."',
+				homepage='$homepage',
+				short_description='".htmlspecialchars($short_description)."',
+				use_mail='$use_mail',
+				use_survey='0',
+				use_forum='$use_forum',
+				use_pm='$use_pm',
+				use_pm_depend_box='$use_pm_depend_box',
+				use_scm='$use_scm',
+				use_news='$use_news',
+				use_docman='$use_docman',
+				new_doc_address='$new_doc_address',
+				send_all_docs='$send_all_docs',
+		";
+		if ($enable_pserver != '') {
+		$sql .= "
+				enable_pserver='$enable_pserver',
+		";
+		}
+		if ($enable_anonscm != '') {
+		$sql .= "
+				enable_anonscm='$enable_anonscm',
+		";
+		}
+		$sql .= "
+				use_ftp='$use_ftp',
+				use_tracker='$use_tracker',
+				use_frs='$use_frs',
+				use_stats='$use_stats'
+			WHERE group_id='".$this->getID()."'
+		";
+		$res = db_query($sql);
+
+		if (!$res || db_affected_rows($res) < 1) {
+			$this->setError('Error updating project information: '.db_error());
+			db_rollback();
+			return false;
+		}
+
+		// Log the audit trail
+		$this->addHistory('Changed Public Info', '');
+
+		if (!$this->fetchData($this->getID())) {
+			db_rollback();
+			return false;
+		}
+		db_commit();
+		return true;
+	}
+
+	/**
+	 *	getID - Simply return the group_id for this object.
+	 *
+	 *	@return int group_id.
+	 */
+	function getID() {
+		return $this->data_array['group_id'];
+	}
+
+	/**
+	 *	getType() - Foundry, project, etc.
+	 *
+	 *	@return	int	The type flag from the database.
+	 */
+	function getType() {
+		return $this->data_array['type_id'];
+	}
+
+
+	/**
+	 *	getStatus - the status code.
+	 *
+	 *	Statuses	char	include I,H,A,D.
+	 */
+	function getStatus() {
+		return $this->data_array['status'];
+	}
+
+	/**
+	 *	setStatus - set the status code.
+	 *
+	 *	Statuses include I,H,A,D.
+	 *
+	 *	@param	object	User requesting operation (for access control).
+	 *	@param	string	Status value.
+	 *	@return	boolean	success.
+	 *	@access public.
+	 */
+	function setStatus(&$user, $status) {
+		global $Language,$SYS;
+
+		$perm =& $this->getPermission($user);
+		if (!$perm || !is_object($perm)) {
+			$this->setPermissionDeniedError();
+			return false;
+		} elseif (!$perm->isSuperUser()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		//	Projects in 'A' status can only go to 'H' or 'D'
+		//	Projects in 'D' status can only go to 'A'
+		//	Projects in 'P' status can only go to 'A' OR 'D'
+		//	Projects in 'I' status can only go to 'P'
+		//	Projects in 'H' status can only go to 'A' OR 'D'
+		$allowed_status_changes = array(
+			'AH'=>1,'AD'=>1,'DA'=>1,'PA'=>1,'PD'=>1,
+			'IP'=>1,'HA'=>1,'HD'=>1
+		);			  
+
+		// Check that status transition is valid
+		if ($this->getStatus() != $status
+			&& !$allowed_status_changes[$this->getStatus().$status]) {
+			$this->setError('Invalid Status Change');
+			return false;
+		}
+
+		db_begin();
+
+		$res = db_query("UPDATE groups
+			SET status='$status'
+			WHERE group_id='". $this->getID()."'");
+
+		if (!$res || db_affected_rows($res) < 1) {
+			$this->setError('ERROR: DB: Could not change group status: '.db_error());
+			db_rollback();
+			return false;
+		}
+
+		if ($status=='A') {
+			// Activate system group, if not yet
+			if (!$SYS->sysCheckGroup($this->getID())) {
+				if (!$SYS->sysCreateGroup($this->getID())) {
+					$this->setError($SYS->getErrorMessage());
+					db_rollback();
+					return false;
+				}
+			}
+			if (!$this->activateUsers()) {
+				db_rollback();
+				return false;
+			}
+
+		/* Otherwise, the group is not active, and make sure that
+		   System group is not active either */
+		} else if ($SYS->sysCheckGroup($this->getID())) {
+			if (!$SYS->sysRemoveGroup($this->getID())) {
+				$this->setError($SYS->getErrorMessage());
+				db_rollback();
+				return false;
+			}
+		}
+
+		db_commit();
+
+		// Log the audit trail
+		if ($status != $this->getStatus()) {
+			$this->addHistory('status', $this->getStatus());
+		}
+
+		$this->data_array['status'] = $status;
+		return true;
+	}
+
+	/**
+	 *	isProject - Simple boolean test to see if it's a project or not.
+	 *
+	 *	@return	boolean is_project.
+	 */
+	function isProject() {
+		if ($this->getType()==1) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 *	isPublic - Simply returns the is_public flag from the database.
+	 *
+	 *	@return	boolean	is_public.
+	 */
+	function isPublic() {
+		return $this->data_array['is_public'];
+	}
+
+	/**
+	 *	isActive - Database field status of 'A' returns true.
+	 *
+	 *	@return	boolean	is_active.
+	 */
+	function isActive() {
+		if ($this->getStatus()=='A') {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 *  getUnixName - the unix_name
+	 *
+	 *  @return	string	unix_name.
+	 */
+	function getUnixName() {
+		return strtolower($this->data_array['unix_group_name']);
+	}
+
+	/**
+	 *  getPublicName - the full-length public name.
+	 *
+	 *  @return	string	The group_name.
+	 */
+	function getPublicName() {
+		return htmlspecialchars($this->data_array['group_name']);
+	}
+
+	/**
+	 *  getRegisterPurpose - the text description of the purpose of this project.
+	 *
+	 *  @return	string	The description.
+	 */
+	function getRegisterPurpose() {
+		return $this->data_array['register_purpose'];
+	}
+
+	/**
+	 *  getDescription	- the text description of this project.
+	 *
+	 *  @return	string	The description.
+	 */
+	function getDescription() {
+		return $this->data_array['short_description'];
+	}
+
+	/**
+	 *  getStartDate - the unix time this project was registered.
+	 *
+	 *  @return int (unix time) of registration.
+	 */
+	function getStartDate() {
+		return $this->data_array['register_time'];
+	}
+
+	/**
+	 *  getLogoImageID - the id of the logo in the database for this project.
+	 *
+	 *  @return	int	The ID of logo image in db_images table (or 100 if none).
+	 */
+	function getLogoImageID() {
+		return $this->data_array['logo_image_id'];
+	}
+
+	/**
+	 *  getUnixBox - the hostname of the unix box where this project is located.
+	 *
+	 *  @return	string	The name of the unix machine for the group.
+	 */
+	function getUnixBox() {
+		return $this->data_array['unix_box'];
+	}
+
+	/**
+	 *  getSCMBox - the hostname of the scm box where this project is located.
+	 *
+	 *  @return	string	The name of the unix machine for the group.
+	 */
+	function getSCMBox() {
+		return $this->data_array['scm_box'];
+	}
+	/**
+	 * setSCMBox - the hostname of the scm box where this project is located.
+	 *
+	 * @param	string The name of the new SCM_BOX
+	 */
+	function setSCMBox($scm_box) {
+		global $Language;
+
+		if ($scm_box) {
+			db_begin();
+			$sql = "UPDATE groups SET scm_box = '$scm_box' WHERE group_id = ".$this->getID();
+			$res = db_query($sql);
+			if ($res) {
+				$this->addHistory('scm_box', $this->data_array['scm_box']);
+				$this->data_array['scm_box']=$scm_box;
+				db_commit();
+				return true;
+			} else {
+				db_rollback();
+				$this->setError('Couldn\'t insert SCM_BOX to database');
+				return false;
+			}
+		} else {
+			$this->setError($Language->getText('admin','scm_box_empty'));
+			return false;
+		}
+	}
+
+	/**
+	 *  getDomain - the hostname.domain where their web page is located.
+	 *
+	 *  @return	string	The name of the group [web] domain.
+	 */
+	function getDomain() {
+		return $this->data_array['http_domain'];
+	}
+
+	/**
+	 *  getLicense	- the license they chose.
+	 *
+	 *  @return	int	ident of group license.
+	 */
+	function getLicense() {
+		return $this->data_array['license'];
+	}
+	
+	/**
+	 * getLicenseName - the name of the license
+	 *
+	 * @return string license name
+	 */
+	function getLicenseName() {
+		$licenses =& group_get_licenses();
+		if(isset($licenses[$this->data_array['license']])) {
+			return $licenses[$this->data_array['license']];
+		} else {
+			return '';
+		}
+	}
+
+	/**
+	 *  getLicenseOther - optional string describing license.
+	 *
+	 *  @return	string	The custom license.
+	 */
+	function getLicenseOther() {
+		if ($this->getLicense() == GROUP_LICENSE_OTHER) {
+			return $this->data_array['license_other'];
+		} else {
+			return '';
+		}
+	}
+
+	/**
+	 *  getRegistrationPurpose - the text description of the purpose of this project.
+	 *
+	 *  @return	string	The application for project hosting.
+	 */
+	function getRegistrationPurpose() {
+		return $this->data_array['register_purpose'];
+	}
+
+
+	/**
+	 * getAdmins() - Get array of Admin user objects.
+	 *
+	 *	@return	array	Array of User objects.
+	 */
+	function &getAdmins() {
+		// this function gets all group admins in order to send Jabber and mail messages
+		$q = "SELECT user_id FROM user_group WHERE admin_flags = 'A' AND group_id = ".$this->getID();
+		$res = db_query($q);
+		$user_ids=util_result_column_to_array($res);
+		return user_get_objects($user_ids);
+	}
+		
+	/*
+
+		Common Group preferences for tools
+
+	*/
+
+	/**
+	 *	enableAnonSCM - whether or not this group has opted to enable Anonymous SCM.
+	 *
+	 *	@return boolean enable_scm.
+	 */
+	function enableAnonSCM() {
+		if ($this->isPublic() && $this->usesSCM()) {
+			return $this->data_array['enable_anonscm'];
+		} else {
+			return false;
+		}
+	}
+
+	function SetUsesAnonSCM ($booleanparam) {
+		db_begin () ;
+		$booleanparam = $booleanparam ? 1 : 0 ;
+		$sql = "UPDATE groups SET enable_anonscm = $booleanparam WHERE group_id = ".$this->getID() ;
+		$res = db_query($sql);
+		if ($res) {
+			$this->data_array['enable_anonscm']=$booleanparam;
+			db_commit () ;
+		} else {
+			db_rollback ();
+			return false;
+		}
+	}
+
+	/**
+	 *	enablePserver - whether or not this group has opted to enable Pserver.
+	 *
+	 *	@return boolean	enable_pserver.
+	 */
+	function enablePserver() {
+		if ($this->usesSCM()) {
+			return $this->data_array['enable_pserver'];
+		} else {
+			return false;
+		}
+	}
+
+	function SetUsesPserver ($booleanparam) {
+		db_begin () ;
+		$booleanparam = $booleanparam ? 1 : 0 ;
+		$sql = "UPDATE groups SET enable_pserver = $booleanparam WHERE group_id = ".$this->getID() ;
+		$res = db_query($sql);
+		if ($res) {
+			$this->data_array['enable_pserver']=$booleanparam;
+			db_commit () ;
+		} else {
+			db_rollback();
+			return false;
+		}
+	}
+
+	/**
+	 *	usesSCM - whether or not this group has opted to use SCM.
+	 *
+	 *	@return	boolean	uses_scm.
+	 */
+	function usesSCM() {
+		global $sys_use_scm;
+		if ($sys_use_scm) {
+			return $this->data_array['use_scm'];
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 *	usesMail - whether or not this group has opted to use mailing lists.
+	 *
+	 *	@return	boolean uses_mail.
+	 */
+	function usesMail() {
+		global $sys_use_mail;
+		if ($sys_use_mail) {
+			return $this->data_array['use_mail'];
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * 	usesNews - whether or not this group has opted to use news.
+	 *
+	 *	@return	boolean	uses_news.
+	 */
+	function usesNews() {
+		global $sys_use_news;
+		if ($sys_use_news) {
+			return $this->data_array['use_news'];
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 *	usesForum - whether or not this group has opted to use discussion forums.
+	 *
+	 *  @return	boolean	uses_forum.
+	 */
+	function usesForum() {
+		global $sys_use_forum;
+		if ($sys_use_forum) {
+			return $this->data_array['use_forum'];
+		} else {
+			return false;
+		}
+	}	   
+
+	/**
+	 *  usesStats - whether or not this group has opted to use stats.
+	 *
+	 *  @return	boolean	uses_stats.
+	 */
+	function usesStats() {
+		return $this->data_array['use_stats'];
+	}
+
+	/**
+	 *  usesFRS - whether or not this group has opted to use file release system.
+	 *
+	 *  @return	boolean	uses_frs.
+	 */
+	function usesFRS() {
+		global $sys_use_frs;
+		if ($sys_use_frs) {
+			return $this->data_array['use_frs'];
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 *  usesTracker - whether or not this group has opted to use tracker.
+	 *
+	 *  @return	boolean	uses_tracker.
+	 */
+	function usesTracker() {
+		global $sys_use_tracker;
+		if ($sys_use_tracker) {
+			return $this->data_array['use_tracker'];
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 *  usesDocman - whether or not this group has opted to use docman.
+	 *
+	 *  @return	boolean	uses_docman.
+	 */
+	function usesDocman() {
+		global $sys_use_docman;
+		if ($sys_use_docman) {
+			return $this->data_array['use_docman'];
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 *  usesFTP - whether or not this group has opted to use FTP.
+	 *
+	 *  @return	boolean	uses_ftp.
+	 */
+	function usesFTP() {
+		global $sys_use_ftp;
+		if ($sys_use_ftp) {
+			return $this->data_array['use_ftp'];
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 *  usesPM - whether or not this group has opted to Project Manager.
+	 *
+	 *  @return	boolean	uses_projman.
+	 */
+	function usesPM() {
+		global $sys_use_pm;
+		if ($sys_use_pm) {
+			return $this->data_array['use_pm'];
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 *  getPlugins -  get a list of all available group plugins
+	 *
+	 *  @return	array	array containing plugin_id => plugin_name
+	 */
+	function getPlugins() {
+		if (!isset($this->plugins_data)) {
+			$this->plugins_data = array () ;
+			$sql="SELECT group_plugin.plugin_id, plugins.plugin_name
+							  FROM group_plugin, plugins
+				  WHERE group_plugin.group_id=".$this->getID()."
+								AND group_plugin.plugin_id = plugins.plugin_id" ;
+			$res=db_query($sql);
+			$rows = db_numrows($res);
+
+			for ($i=0; $i<$rows; $i++) {
+				$plugin_id = db_result($res,$i,'plugin_id');
+				$this->plugins_data[$plugin_id] = db_result($res,$i,'plugin_name');
+			}
+		}
+		return $this->plugins_data ;
+	}
+
+	/**
+	 *  usesPlugin - returns true if the group uses a particular plugin 
+	 *
+	 *  @param	string	name of the plugin
+	 *  @return	boolean	whether plugin is being used or not
+	 */
+	function usesPlugin($pluginname) {
+		$plugins_data = $this->getPlugins() ;
+		foreach ($plugins_data as $p_id => $p_name) {
+			if ($p_name == $pluginname) {
+				return true ;
+			}
+		}
+		return false ;
+	}
+
+	/**
+	 *  setPluginUse - enables/disables plugins for the group
+	 *
+	 *  @param	string	name of the plugin
+	 *  @param	boolean	the new state
+	 *  @return	string	database result 
+	 */
+	function setPluginUse($pluginname, $val=true) {
+		if ($val == $this->usesPlugin($pluginname)) {
+			// State is already good, returning
+			return true ;
+		}
+		$sql="SELECT plugin_id
+			  FROM plugins
+			  WHERE plugin_name = '" . $pluginname . "'" ;
+		$res=db_query($sql);
+		$rows = db_numrows($res);
+		if ($rows == 0) {
+			// Error: no plugin by that name
+			return false ;
+		}
+		$plugin_id = db_result($res,0,'plugin_id');
+		// Invalidate cache
+		unset ($this->plugins_data) ;
+		if ($val) {
+			$sql="INSERT INTO group_plugin (group_id, plugin_id)
+							  VALUES (". $this->getID() . ", ". $plugin_id .")" ;
+			$res=db_query($sql);
+			return $res ;
+		} else {
+			$sql="DELETE FROM group_plugin
+				WHERE group_id = ". $this->getID() . "
+				AND plugin_id = ". $plugin_id ;
+			$res=db_query($sql);
+			return $res ;
+		}
+	}
+
+	/**
+	 *  getDocEmailAddress - get email address(es) to send doc notifications to.
+	 *
+	 *  @return	string	email address.
+	 */
+	function getDocEmailAddress() {
+		return $this->data_array['new_doc_address'];
+	}
+
+	/**
+	 *  DocEmailAll - whether or not this group has opted to use receive notices on all doc updates.
+	 *
+	 *  @return	boolean	email_on_all_doc_updates.
+	 */
+	function docEmailAll() {
+		return $this->data_array['send_all_docs'];
+	}
+
+
+	/**
+	 *	getHomePage - The URL for this project's home page.
+	 *
+	 *	@return	string	homepage URL.
+	 */
+	function getHomePage() {
+		return $this->data_array['homepage'];
+	}
+
+	/**
+	 *	getPermission - Return a Permission for this Group and the specified User.
+	 *
+	 *	@param	object	The user you wish to get permission for (usually the logged in user).
+	 *	@return	object	The Permission.
+	 */
+	function &getPermission(&$_user) {
+		return permission_get_object($this, $_user);
+	}
+
+
+	/**
+	 *	userIsAdmin - Return if for this Group the User is admin.
+	 *
+	 *	@return boolean	is_admin.
+	 */
+	function userIsAdmin() {
+		$perm =& $this->getPermission( session_get_user() );
+		if (!$perm || !is_object($perm)) {
+			return false;
+		} elseif ($perm->isError()) {
+			return false;
+		}
+		return $perm->isAdmin();
+	}
+
+	function delete($sure,$really_sure,$really_really_sure) {
+		global $Language;
+
+		if (!$sure || !$really_sure || !$really_really_sure) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		if ($this->getID() == $GLOBALS['sys_news_group'] ||
+			$this->getID() == 1 ||
+			$this->getID() == $GLOBALS['sys_stats_group']) {
+			$this->setError('Cannot Delete System Group');
+			return false;
+		}
+		$perm =& $this->getPermission( session_get_user() );
+		if (!$perm || !is_object($perm)) {
+			$this->setPermissionDeniedError();
+			return false;
+		} elseif ($perm->isError()) {
+			$this->setPermissionDeniedError();
+			return false;
+		} elseif (!$perm->isSuperUser()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		db_begin();
+		//
+		//	Remove all the members
+		//
+		$members =& $this->getMembers();
+		for ($i=0; $i<count($members); $i++) {
+			$this->removeUser($members[$i]->getID());
+//echo 'RemoveMembers'.db_error();
+		}
+		//
+		//	Delete Trackers
+		//
+		$atf = new ArtifactTypeFactory($this);
+		$at_arr =& $atf->getArtifactTypes();
+		for ($i=0; $i<count($at_arr); $i++) {
+			if (!is_object($at_arr[$i])) {
+				echo "Not Object: ArtifactType: ".$i;
+				continue;
+			}
+			$at_arr[$i]->delete(1,1);
+//echo 'ArtifactTypeFactory'.db_error();
+		}
+		//
+		//	Delete Forums
+		//
+		$ff = new ForumFactory($this);
+		$f_arr =& $ff->getForums();
+		for ($i=0; $i<count($f_arr); $i++) {
+			if (!is_object($f_arr[$i])) {
+				echo "Not Object: Forum: ".$i;
+				continue;
+			}
+			$f_arr[$i]->delete(1,1);
+//echo 'ForumFactory'.db_error();
+		}
+		//
+		//	Delete Subprojects
+		//
+		$pgf = new ProjectGroupFactory($this);
+		$pg_arr =& $pgf->getProjectGroups();
+		for ($i=0; $i<count($pg_arr); $i++) {
+			if (!is_object($pg_arr[$i])) {
+				echo "Not Object: ProjectGroup: ".$i;
+				continue;
+			}
+			$pg_arr[$i]->delete(1,1);
+//echo 'ProjectGroupFactory'.db_error();
+		}
+		//
+		//	Delete FRS Packages
+		//
+		//$frspf = new FRSPackageFactory($this);
+		$res=db_query("SELECT * FROM frs_package WHERE group_id='".$this->getID()."'");
+//echo 'frs_package'.db_error();
+		//$frsp_arr =& $frspf->getPackages();
+		while ($arr = db_fetch_array($res)) {
+			//if (!is_object($pg_arr[$i])) {
+			//	echo "Not Object: ProjectGroup: ".$i;
+			//	continue;
+			//}
+			$frsp=new FRSPackage($this,$arr['package_id'],$arr);
+			$frsp->delete(1,1);
+		}
+		//
+		//	Delete news
+		//
+		$news_group=&group_get_object($GLOBALS['sys_news_group']);
+		$res=db_query("SELECT forum_id FROM news_bytes WHERE group_id='".$this->getID()."'");
+		for ($i=0; $i<db_numrows($res); $i++) {
+			$Forum = new Forum($news_group,db_result($res,$i,'forum_id'));
+			if (!$Forum->delete(1,1)) {
+				echo "Could Not Delete News Forum: ".$Forum->getID();
+			}
+		}
+		$res=db_query("DELETE FROM news_bytes WHERE group_id='".$this->getID()."'");
+
+		//
+		//	Delete docs
+		//
+		$res=db_query("DELETE FROM doc_data WHERE group_id='".$this->getID()."'");
+//echo 'doc_data'.db_error();
+		$res=db_query("DELETE FROM doc_groups WHERE group_id='".$this->getID()."'");
+//echo 'doc_groups'.db_error();
+		//
+		//	Delete group history
+		//
+		$res=db_query("DELETE FROM group_history WHERE group_id='".$this->getID()."'");
+//echo 'group_history'.db_error();
+		//
+		//	Delete group plugins
+		//
+		$res=db_query("DELETE FROM group_plugin WHERE group_id='".$this->getID()."'");
+//echo 'group_plugin'.db_error();
+		//
+		//	Delete group cvs stats
+		//
+		$res=db_query("DELETE FROM stats_cvs_group WHERE group_id='".$this->getID()."'");
+//echo 'stats_cvs_group'.db_error();
+		//
+		//	Delete Mailing List Factory
+		//
+		$mlf = new MailingListFactory($this);
+		$ml_arr =& $mlf->getMailingLists();
+		for ($i=0; $i<count($ml_arr); $i++) {
+			if (!is_object($ml_arr[$i])) {
+				echo "Not Object: MailingList: ".$i;
+				continue;
+			}
+			if (!$ml_arr[$i]->delete(1,1)) {
+				$this->setError($Language->getText('mail','error_delete'));
+			}
+
+//echo 'MailingListFactory'.db_error();
+		}
+		//
+		//	Delete trove
+		//
+		$res=db_query("DELETE FROM trove_group_link WHERE group_id='".$this->getID()."'");
+		$res=db_query("DELETE FROM trove_agg WHERE group_id='".$this->getID()."'");
+		//
+		//	Delete counters
+		//
+		$res=db_query("DELETE FROM project_sums_agg WHERE group_id='".$this->getID()."'");
+//echo 'project_sums_agg'.db_error();
+		$res=db_query("INSERT INTO deleted_groups (
+		unix_group_name,delete_date,isdeleted) VALUES 
+		('".$this->getUnixName()."','".time()."','0')");
+//echo 'InsertIntoDeleteQueue'.db_error();
+		$res=db_query("DELETE FROM groups WHERE group_id='".$this->getID()."'");
+//echo 'DeleteGroup'.db_error();
+		db_commit();
+		if (!$res) {
+			return false;
+		}
+		if (isset($GLOBALS['sys_upload_dir']) && $this->getUnixName()) {
+			exec('/bin/rm -rf '.$GLOBALS['sys_upload_dir'].'/'.$this->getUnixName().'/');
+		}
+		if (isset($GLOBALS['sys_ftp_upload_dir']) && $this->getUnixName()) {
+			exec('/bin/rm -rf '.$GLOBALS['sys_ftp_upload_dir'].'/'.$this->getUnixName().'/');
+		}
+		//
+		//	Delete reporting
+		//
+		$res=db_query("DELETE FROM rep_group_act_weekly WHERE group_id='".$this->getID()."'");
+//echo 'rep_group_act_weekly'.db_error();
+		$res=db_query("DELETE FROM rep_group_act_monthly WHERE group_id='".$this->getID()."'");
+//echo 'rep_group_act_monthly'.db_error();
+		$res=db_query("DELETE FROM rep_group_act_daily WHERE group_id='".$this->getID()."'");
+//echo 'rep_group_act_daily'.db_error();
+		unset($this->data_array);
+		return true;
+
+	}
+
+	/*
+
+
+		Basic functions to add/remove users to/from a group
+		and update their permissions
+
+
+	*/
+
+	/**
+	 *	addUser - controls adding a user to a group.
+	 *  
+	 *  @param	string	Unix name of the user to add OR integer user_id.
+	 *	@param	int	The role_id this user should have.
+	 *	@return	boolean	success.
+	 *	@access public.
+	 */
+	function addUser($user_unix_name,$role_id) {
+		global $Language,$SYS;
+		/*
+			Admins can add users to groups
+		*/
+
+		$perm =& $this->getPermission( session_get_user() );
+		if (!$perm || !is_object($perm) || !$perm->isAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		db_begin();
+		
+		/*
+			get user id for this user's unix_name
+		*/
+		if (eregi('[^0-9]',$user_unix_name)) {
+			$res_newuser = db_query("SELECT * FROM users WHERE user_name='". strtolower($user_unix_name) ."'");
+		} else {
+			$res_newuser = db_query("SELECT * FROM users WHERE user_id='". intval($user_unix_name) ."'");
+		}
+		if (db_numrows($res_newuser) > 0) {
+			//
+			//	make sure user is active
+			//
+			if (db_result($res_newuser,0,'status') != 'A') {
+				$this->setError('User is not active. Only active users can be added.');
+				db_rollback();
+				return false;
+			}
+
+			//
+			//	user was found - set new user_id var
+			//
+			$user_id = db_result($res_newuser,0,'user_id');
+
+			//
+			//	if not already a member, add them
+			//
+			$res_member = db_query("SELECT user_id 
+				FROM user_group 
+				WHERE user_id='$user_id' AND group_id='". $this->getID() ."'");
+
+			if (db_numrows($res_member) < 1) {
+				//
+				//	Create this user's row in the user_group table
+				//
+				$res=db_query("INSERT INTO user_group 
+					(user_id,group_id,admin_flags,forum_flags,project_flags,
+					doc_flags,cvs_flags,member_role,release_flags,artifact_flags)
+					VALUES ('$user_id','". $this->getID() ."','','0','0','0','1','100','0','0')");
+
+				//verify the insert worked
+				if (!$res || db_affected_rows($res) < 1) {
+					$this->setError('ERROR: Could Not Add User To Group: '.db_error());
+					db_rollback();
+					return false;
+				}
+				//
+				//	Add to all forums
+				//
+				$sql="INSERT INTO forum_perm (group_forum_id,user_id,perm_level) 
+					SELECT group_forum_id,$user_id,1
+					FROM forum_group_list 
+					WHERE group_id='".$this->getID()."'";
+				$res=db_query($sql);
+				if (!$res) {
+					$this->setError('Adding to forums: '.db_error());
+					db_rollback();
+					return false;
+				}
+				//
+				//	Add to all subprojects
+				//
+				$sql="INSERT INTO project_perm (group_project_id,user_id,perm_level) 
+					SELECT group_project_id,$user_id,2
+					FROM project_group_list 
+					WHERE group_id='".$this->getID()."'";
+				$res=db_query($sql);
+				if (!$res) {
+					$this->setError('Adding to subprojects: '.db_error());
+					db_rollback();
+					return false;
+				}
+				//
+				//	Add to all trackers
+				//
+				$sql="INSERT INTO artifact_perm (group_artifact_id,user_id,perm_level) 
+					SELECT group_artifact_id,$user_id,2
+					FROM artifact_group_list 
+					WHERE group_id='".$this->getID()."'";
+				$res=db_query($sql);
+				if (!$res) {
+					$this->setError('Adding to subprojects: '.db_error());
+					db_rollback();
+					return false;
+				}
+
+				//
+				//	check and create if group doesn't exists
+				//
+//echo "<h2>Group::addUser SYS->sysCheckCreateGroup(".$this->getID().")</h2>";
+				if (!$SYS->sysCheckCreateGroup($this->getID())){
+					$this->setError($SYS->getErrorMessage());
+					db_rollback();
+					return false;
+				}
+				//
+				//	check and create if user doesn't exists
+				//
+//echo "<h2>Group::addUser SYS->sysCheckCreateUser($user_id)</h2>";
+				if (!$SYS->sysCheckCreateUser($user_id)) {
+					$this->setError($SYS->getErrorMessage());
+					db_rollback();
+					return false;
+				}
+				//
+				//	Role setup
+				//
+				$role = new Role($this,$role_id);
+				if (!$role || !is_object($role)) {
+					$this->setError('Error Getting Role Object');
+					db_rollback();
+					return false;
+				} elseif ($role->isError()) {
+					$this->setError('addUser::roleget::'.$role->getErrorMessage());
+					db_rollback();
+					return false;
+				}
+//echo "<h2>Group::addUser role->setUser($user_id)</h2>";
+				if (!$role->setUser($user_id)) {
+					$this->setError('addUser::role::setUser'.$role->getErrorMessage());
+					db_rollback();
+					return false;
+				}
+			} else {
+				//
+				//  user was already a member
+				//  make sure they are set up 
+				//
+				$user=&user_get_object($user_id,$res_newuser);
+				$user->fetchData($user->getID());
+				$role = new Role($this,$role_id);
+				if (!$role || !is_object($role)) {
+					$this->setError('Error Getting Role Object');
+					db_rollback();
+					return false;
+				} elseif ($role->isError()) {
+					$this->setError('addUser::roleget::'.$role->getErrorMessage());
+					db_rollback();
+					return false;
+				}
+//echo "<h2>Already Member Group::addUser role->setUser($user_id)</h2>";
+				if (!$role->setUser($user_id)) {
+					$this->setError('addUser::role::setUser'.$role->getErrorMessage());
+					db_rollback();
+					return false;
+				}
+				//
+				//	set up their system info
+				//
+//echo "<h2>Already Member Group::addUser SYS->sysCheckCreateUser($user_id)</h2>";
+				if (!$SYS->sysCheckCreateUser($user_id)) {
+					$this->setError($SYS->getErrorMessage());
+					db_rollback();
+					return false;
+				}
+				db_commit();
+				return true;
+			}
+		} else {
+			//
+			//	user doesn't exist
+			//
+			$this->setError('ERROR: User does not exist');
+			db_rollback();
+			return false;
+		}
+		//
+		//	audit trail
+		//
+		$this->addHistory('Added User',$user_unix_name);
+		db_commit();
+		return true;
+	}
+
+	/**
+	 *  removeUser - controls removing a user from a group.
+	 * 
+	 *  Users can remove themselves.
+	 *
+	 *  @param	int		The ID of the user to remove.
+	 *	@return	boolean	success.
+	 */ 
+	function removeUser($user_id) {
+		global $Language,$SYS;
+
+		if ($user_id==user_getid()) {
+			//users can remove themselves
+			//everyone else must be a project admin
+		} else {
+			$perm =& $this->getPermission( session_get_user() );
+
+			if (!$perm || !is_object($perm) || !$perm->isAdmin()) {
+				$this->setPermissionDeniedError();
+				return false;
+			}
+		}
+	
+		db_begin();
+		$res=db_query("DELETE FROM user_group 
+			WHERE group_id='".$this->getID()."' 
+			AND user_id='$user_id'");
+		if (!$res || db_affected_rows($res) < 1) {
+			$this->setError('ERROR: DB: User not removed.'.db_error());
+			db_rollback();
+			return false;
+		} else {
+			//
+			//	remove them from artifact types
+			//
+			$res=db_query("DELETE FROM artifact_perm 
+				WHERE group_artifact_id 
+				IN (SELECT group_artifact_id 
+				FROM artifact_group_list 
+				WHERE group_id='".$this->getID()."') 
+				AND user_id='$user_id'");
+			if (!$res) {
+				$this->setError('ERROR: DB: artifact_perm.'.db_error());
+				db_rollback();
+				return false;
+			}
+			//
+			//	remove them from subprojects
+			//
+			$res=db_query("DELETE FROM project_perm 
+				WHERE group_project_id 
+				IN (SELECT group_project_id 
+				FROM project_group_list 
+				WHERE group_id='".$this->getID()."') 
+				AND user_id='$user_id'");
+			if (!$res) {
+				$this->setError('ERROR: DB: project_perm.'.db_error());
+				db_rollback();
+				return false;
+			}
+			//
+			//	remove them from forums
+			//
+			$res=db_query("DELETE FROM forum_perm 
+				WHERE group_forum_id 
+				IN (SELECT group_forum_id 
+				FROM forum_group_list 
+				WHERE group_id='".$this->getID()."') 
+				AND user_id='$user_id'");
+			if (!$res) {
+				$this->setError('ERROR: DB: forum_perm.'.db_error());
+				db_rollback();
+				return false;
+			}
+
+			//
+			//	reassign open artifacts to id=100
+			//
+			$res=db_query("UPDATE artifact SET assigned_to='100' 
+				WHERE group_artifact_id 
+				IN (SELECT group_artifact_id 
+				FROM artifact_group_list 
+				WHERE group_id='".$this->getID()."') 
+				AND status_id='1' AND assigned_to='$user_id'");
+			if (!$res) {
+				$this->setError('ERROR: DB: artifact.'.db_error());
+				db_rollback();
+				return false;
+			}
+
+			//
+			//	reassign open tasks to id=100
+			//	first have to purge any assignments that would cause 
+			//	conflict with existing assignment to 100
+			//
+			$res=db_query("DELETE FROM project_assigned_to
+				WHERE project_task_id IN (SELECT pt.project_task_id 
+				FROM project_task pt, project_group_list pgl, project_assigned_to pat 
+				WHERE pt.group_project_id = pgl.group_project_id 
+				AND pat.project_task_id=pt.project_task_id
+				AND pt.status_id='1' AND pgl.group_id='".$this->getID()."'
+				AND pat.assigned_to_id='$user_id') 
+				AND assigned_to_id='100'");
+			if (!$res) {
+				$this->setError('ERROR: DB: project_assigned_to 1'.db_error());
+				db_rollback();
+				return false;
+			}
+			$res=db_query("UPDATE project_assigned_to SET assigned_to_id='100' 
+				WHERE project_task_id IN (SELECT pt.project_task_id 
+				FROM project_task pt, project_group_list pgl 
+				WHERE pt.group_project_id = pgl.group_project_id 
+				AND pt.status_id='1' AND pgl.group_id='".$this->getID()."') 
+				AND assigned_to_id='$user_id'");
+			if (!$res) {
+				$this->setError('ERROR: DB: project_assigned_to.'.db_error());
+				db_rollback();
+				return false;
+			}
+
+			//
+			//	Remove user from system
+			//
+//echo "<h2>Group::addUser SYS->sysGroupRemoveUser(".$this->getID().",$user_id)</h2>";
+			if (!$SYS->sysGroupRemoveUser($this->getID(),$user_id)) {
+				$this->setError($SYS->getErrorMessage());
+				db_rollback();
+				return false;
+			}
+			//audit trail
+			$this->addHistory('removed user',$user_id);
+
+		}
+		db_commit();
+		return true;
+	}
+
+	/**	 
+	 *  updateUser - controls updating a user's role in this group.
+	 *
+	 *  @param	int		The ID of the user.
+	 *	@param	int		The role_id to set this user to.
+	 *	@return	boolean	success.
+	 */	 
+	function updateUser($user_id,$role_id) {
+		global $Language,$SYS;
+
+		$perm =& $this->getPermission( session_get_user() );
+		if (!$perm || !is_object($perm) || !$perm->isAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		$role = new Role($this,$role_id);
+		if (!$role || !is_object($role)) {
+			$this->setError('Could Not Get Role');
+			return false;
+		} elseif ($role->isError()) {
+			$this->setError('Role: '.$role->getErrorMessage());
+			return false;
+		}
+//echo "<h3>Group::updateUser role->setUser($user_id)</h3>";
+		if (!$role->setUser($user_id)) {
+			$this->setError('Role: '.$role->getErrorMessage());
+			return false;
+		}
+		$this->addHistory('updated user',$user_id);
+		return true;
+	}
+
+	/**
+	 *	addHistory - Makes an audit trail entry for this project.
+	 *
+	 *  @param	string	The name of the field.
+	 *  @param	string	The Old Value for this $field_name.
+	 *	@return database result handle.
+	 *	@access public.
+	 */
+	function addHistory($field_name, $old_value) {
+		$sql="
+			INSERT INTO group_history(group_id,field_name,old_value,mod_by,adddate) 
+			VALUES ('". $this->getID() ."','$field_name','$old_value','". user_getid() ."','".time()."')
+		";
+		return db_query($sql);
+	}		  
+
+	/**
+	 *	activateUsers - Make sure that group members have unix accounts.
+	 *
+	 *	Setup unix accounts for group members. Can be called even
+	 *	if members are already active. 
+	 *
+	 *	@access private.
+	 */
+	function activateUsers() {
+
+		/*
+			Activate member(s) of the project
+		*/
+
+		$member_res = db_query("SELECT user_id, role_id
+			FROM user_group
+			WHERE group_id='".$this->getID()."'");
+
+		$rows = db_numrows($member_res);
+
+		if ($rows > 0) {
+
+			for ($i=0; $i<$rows; $i++) {
+
+				$member =& user_get_object(db_result($member_res,$i,'user_id'));
+				$roleId = db_result($member_res,$i,'role_id');
+
+				if (!$member || !is_object($member)) {
+					$this->setError('Error getting member object');
+					return false;
+				} else if ($member->isError()) {
+					$this->setError('Error getting member object: '.$member->getErrorMessage());
+					return false;
+				}
+
+				if (!$this->addUser($member->getUnixName(),$roleId)) {
+					return false;
+				}
+			}
+
+		 }
+
+		 return true;
+	}
+
+	/**
+	 *	getMembers - returns array of User objects for this project
+	 *
+	 *	@return array of User objects for this group.
+	 */
+	function &getMembers() {
+		if (!isset($this->membersArr)) {
+			$res=db_query("SELECT users.* FROM users
+				INNER JOIN user_group ON users.user_id=user_group.user_id
+				WHERE user_group.group_id='".$this->getID()."'");
+			while ($arr =& db_fetch_array($res)) {
+				$this->membersArr[] =& new User($arr['user_id'],$arr);
+			}
+		}
+		return $this->membersArr;
+	}
+
+	/**
+	 *	approve - Approve pending project.
+	 *
+	 *	@param	object	The User object who is doing the updating.
+	 *	@access public
+	 */
+	function approve(&$user) {
+
+		if ($this->getStatus()=='A') {
+			$this->setError("Group already active");
+			return false;
+		}
+
+		db_begin();
+
+		// Step 1: Activate group and create LDAP entries
+		if (!$this->setStatus($user, 'A')) {
+			db_rollback();
+			return false;
+		}
+
+		//
+		//
+		//	Tracker Integration
+		//
+		//
+		$ats = new ArtifactTypes($this);
+		if (!$ats || !is_object($ats)) {
+			$this->setError('Error creating ArtifactTypes object');
+			db_rollback();
+			return false;
+		} else if ($ats->isError()) {
+			$this->setError('ATS1 '.$ats->getErrorMessage());
+			db_rollback();
+			return false;
+		}
+		if (!$ats->createTrackers()) {
+			$this->setError('ATS2 '.$ats->getErrorMessage());
+			db_rollback();
+			return false;
+		}
+
+		//
+		//
+		//	Forum Integration
+		//
+		//
+		$f = new Forum($this);
+		if (!$f->create('Open-Discussion','General Discussion',1,'',1,0)) {
+			$this->setError('F1 '.$f->getErrorMessage());
+			db_rollback();
+			return false;
+		}
+		$f = new Forum($this);
+		if (!$f->create('Help','Get Public Help',1,'',1,0)) {
+			$this->setError('F2 '.$f->getErrorMessage());
+			db_rollback();
+			return false;
+		}
+		$f = new Forum($this);
+		if (!$f->create('Developers','Project Developer Discussion',0,'',1,0)) {
+			$this->setError('F3 '.$f->getErrorMessage());
+			db_rollback();
+			return false;
+		}
+
+		//
+		//
+		//	Doc Mgr Integration
+		//
+		//
+		$dg = new DocumentGroup($this);
+		if (!$dg->create('Uncategorized Submissions')) {
+			$this->setError('DG1 '.$dg->getErrorMessage());
+			db_rollback();
+			return false;
+		}
+
+		//
+		//
+		//	FRS integration
+		//
+		//
+		$frs = new FRSPackage($this);
+		if (!$frs->create($this->getUnixName())) {
+			$this->setError('FRSP '.$frs->getErrorMessage());
+			db_rollback();
+			return false;
+		}
+
+		//
+		//
+		//	PM Integration
+		//
+		//
+		$pg = new ProjectGroup($this);
+		if (!$pg->create('To Do','Things We Have To Do',1)) {
+			$this->setError('PG1 '.$pg->getErrorMessage());
+			db_rollback();
+			return false;
+		}
+		$pg = new ProjectGroup($this);
+		if (!$pg->create('Next Release','Items For Our Next Release',1)) {
+			$this->setError('PG2 '.$pg->getErrorMessage());
+			db_rollback();
+			return false;
+		}
+
+		//
+		//
+		//	Set Default Roles
+		//
+		//
+		$role = new Role($this);
+		$todo = array_keys($role->defaults);
+		for ($c=0; $c<count($todo); $c++) {
+			$role = new Role($this);
+			if (!$role->createDefault($todo[$c])) {
+				$this->setError('R'.$c.' '.$role->getErrorMessage());
+				db_rollback();
+				return false;
+			}
+		}
+
+		//
+		//
+		//	Create MailingList
+		//
+		//
+		$mlist = new MailingList($this);
+		$admin_group = db_query("SELECT user_id FROM user_group 
+		WHERE group_id=".$this->getID()." AND admin_flags='A'");
+		if (db_numrows($admin_group) > 0) {
+			$idadmin_group = db_result($admin_group,0,'user_id');
+		}
+		if (!$mlist->create('commits','cvs commits',1,$idadmin_group)) {
+			$this->setError('MailingList: '.$mlist->getErrorMessage());
+			db_rollback();
+			return false;
+		}
+
+		db_commit();
+
+		$this->sendApprovalEmail();
+		$this->addHistory('approved', 'x');
+
+		return true;
+	}
+
+
+
+	/**
+	 *	sendApprovalEmail - Send new project email.
+	 *
+	 *	@return	boolean	success.
+	 *	@access public.
+	 */
+	function sendApprovalEmail() {
+		$res_admins = db_query("
+			SELECT users.user_name,users.email,users.language
+			FROM users,user_group
+			WHERE users.user_id=user_group.user_id
+			AND user_group.group_id='".$this->getID()."'
+			AND user_group.admin_flags='A'
+		");
+
+		if (db_numrows($res_admins) < 1) {
+			$this->setError("Group does not have any administrators.");
+			return false;
+		}
+
+		// send one email per admin
+		while ($row_admins = db_fetch_array($res_admins)) {
+			$l = new BaseLanguage () ;
+			$l->loadLanguageID($row_admins['language']);
+
+			//																			   $2					  $2					$3							  $4						  $5						$6
+			$message=stripcslashes($l->getText('classgroup', 'acceptedproject', array($this->getPublicName(), $this->getUnixName(), $GLOBALS['sys_default_domain'], $GLOBALS['sys_shell_host'], $GLOBALS['sys_scm_host'], $this->getID(), $GLOBALS['sys_name'])));
+	
+			util_send_message($row_admins['email'], $l->getText('classgroup', 'acceptedprojecttitle', array($GLOBALS['sys_name'])), $message);
+		}
+
+		return true;
+	}
+
+
+	/**
+	 *	sendRejectionEmail - Send project rejection email.
+	 *
+	 *	This function sends out a rejection message to a user who
+	 *	registered a project.
+	 *
+	 *	@param	int	The id of the response to use.
+	 *	@param	string	The rejection message.
+	 *	@return completion status.
+	 *	@access public.
+	 */
+	function sendRejectionEmail($response_id, $message="zxcv") {
+		$res_admins = db_query("
+			SELECT u.email, u.language
+			FROM users u, user_group ug
+			WHERE ug.group_id='".$this->getID()."'
+			AND u.user_id=ug.user_id;
+		");
+
+		if (db_numrows($res_admins) < 1) {
+			$this->setError("Group does not have any administrators.");
+			return false;
+		}
+		
+		while ($row_admins = db_fetch_array($res_admins)) {
+			$l = new BaseLanguage () ;
+			$l->loadLanguageID($row_admins['language']);
+
+			$response=stripcslashes($l->getText('classgroup', 'rejectedproject', array($this->getPublicName(), $this->getUnixName(), $GLOBALS['sys_name'])));
+
+			// Check to see if they want to send a custom rejection response
+			if ($response_id == 0) {
+				$response .= stripcslashes($message);
+			} else {
+				$response .= db_result(db_query("
+				SELECT response_text
+				FROM canned_responses
+				WHERE response_id='$response_id'
+			"), 0, "response_text");
+			}
+
+			util_send_message($row_admins['email'], $l->getText('classgroup', 'rejectedprojecttitle', array($GLOBALS['sys_name'])), $response);
+		}
+
+		return true;
+	}
+
+	/**
+	 *	sendNewProjectNotificationEmail - Send new project notification email.
+	 *
+	 *	This function sends out a notification email to the
+	 *	SourceForge admin user when a new project is
+	 *	submitted.
+	 *
+	 *	@return	boolean	success.
+	 *	@access public.
+	 */
+	function sendNewProjectNotificationEmail() {
+
+		$res = db_query("SELECT users.email, users.language
+	 			FROM users,user_group
+				WHERE group_id=1 
+				AND user_group.admin_flags='A'
+				AND users.user_id=user_group.user_id;");
+		
+		if (db_numrows($res) < 1) {
+			$this->setError("There is no administrator to send the mail.");
+			return false;
+		} else {
+			for ($i=0; $i<db_numrows($res) ; $i++) {
+				$admin_email = db_result($res,$i,'email') ;
+				$l = new BaseLanguage () ;
+				$l->loadLanguageID(db_result($res,$i,'language'));
+				
+				$message=stripcslashes($l->getText('classgroup', 'newprojectnotification', array($GLOBALS['sys_name'], $this->getPublicName(), util_unconvert_htmlspecialchars($this->getRegistrationPurpose()), $this->getLicenseName(), $GLOBALS['sys_default_domain'])));
+				util_send_message($admin_email, $l->getText('classgroup', 'newprojectnotificationtitle', array($GLOBALS['sys_name'])), $message);
+			}
+		}
+		
+		// Get the email of the user who wants to register the project
+		$res = db_query("SELECT u.email, u.language
+				 FROM users u, user_group ug
+				 WHERE ug.group_id='".$this->getID()."' AND u.user_id=ug.user_id;");
+
+		if (db_numrows($res) < 1) {
+			$this->setError("Cound not find user who has submitted the project.");
+			return false;
+		} else {
+			for ($i=0; $i<db_numrows($res) ; $i++) {
+				$email = db_result($res, $i, 'email');
+				$l = new BaseLanguage () ;
+				$l->loadLanguageID(db_result($res,$i,'language'));
+				
+				$message=stripcslashes($l->getText('classgroup', 'newprojectnotification_submitter', array($GLOBALS['sys_name'], $this->getPublicName(), util_unconvert_htmlspecialchars($this->getRegistrationPurpose()), $this->getLicenseName(), $GLOBALS['sys_default_domain'])));
+				
+				util_send_message($email, $l->getText('classgroup', 'newprojectnotificationtitle', array($GLOBALS['sys_name'])), $message);
+			}
+		}
+		
+
+	  return true;
+	}
+}
+
+/**
+ * group_getname() - get the group name
+ *
+ * @param	   int	 The group ID
+ * @deprecated
+ *
+ */
+function group_getname ($group_id = 0) {
+	$grp = &group_get_object($group_id);
+	if ($grp) {
+		return $grp->getPublicName();
+	} else {
+		return 'Invalid';
+	}
+}
+
+/**
+ * group_getunixname() - get the unixname for a group
+ *
+ * @param	   int	 The group ID
+ * @deprecated
+ *
+ */
+function group_getunixname ($group_id) {
+	$grp = &group_get_object($group_id);
+	if ($grp) {
+		return $grp->getUnixName();
+	} else {
+		return 'Invalid';
+	}
+}
+
+/**
+ * group_get_result() - Get the group object result ID.
+ *
+ * @param	   int	 The group ID
+ * @deprecated
+ *
+ */
+function &group_get_result($group_id=0) {
+	$grp = &group_get_object($group_id);
+	if ($grp) {
+		return $grp->getData();
+	} else {
+		return 0;
+	}
+}
+
+/**
+ *	getUnixStatus - Status of activation of unix account.
+ *
+ *	@return	char	(N)one, (A)ctive, (S)uspended or (D)eleted
+ */
+function getUnixStatus() {
+	return $this->data_array['unix_status'];
+}
+
+/**
+ *	setUnixStatus - Sets status of activation of unix account.
+ *
+ *	@param	string	The unix status.
+ *	N	no_unix_account
+ *	A	active
+ *	S	suspended
+ *	D	deleted
+ *
+ *	@return	boolean success.
+ */
+function setUnixStatus($status) {
+	global $Language,$SYS;
+	db_begin();
+	$res=db_query("
+		UPDATE groups 
+		SET unix_status='$status' 
+		WHERE group_id='". $this->getID()."'
+	");
+
+	if (!$res) {
+		$this->setError('ERROR - Could Not Update Group Unix Status: '.db_error());
+		db_rollback();
+		return false;
+	} else {
+		if ($status == 'A') {
+			if (!$SYS->sysCheckCreateGroup($this->getID())) {
+				$this->setError($SYS->getErrorMessage());
+				db_rollback();
+				return false;
+			}
+		} else {
+			if ($SYS->sysCheckGroup($this->getID())) {
+				if (!$SYS->sysRemoveGroup($this->getID())) {
+					$this->setError($SYS->getErrorMessage());
+					db_rollback();
+					return false;
+				}
+			}
+		}
+		
+		$this->data_array['unix_status']=$status;
+		db_commit();
+		return true;
+	}
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/include/GroupJoinRequest.class
===================================================================
--- trunk/gforge_base/gforge/common/include/GroupJoinRequest.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/GroupJoinRequest.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,306 @@
+<?php
+/**
+ * Gforge group_join_request Facility
+ *
+ * Copyright 2005 (c) GForge Group
+ *
+ * @version   $Id: GroupJoinRequest.class 4384 2005-05-20 19:16:26Z marcelo $
+ *
+ * This file is part of Gforge.
+ *
+ * Gforge 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.
+ *
+ * Gforge 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 Gforge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+
+require_once('common/include/Error.class');
+require_once('common/include/Validator.class');
+
+/*
+function &groupjoinrequest_get_object($group_id,$user_id,$data=false) {
+	global $GROUPJOINREQUEST_OBJ;
+	if (!isset($GROUPJOINREQUEST_OBJ["_".$group_id."_".$user_id."_"])) {
+		if ($data) {
+			//the db result handle was passed in
+		} else {
+			$res=db_query("SELECT * FROM group_join_request
+				WHERE group_id='$group_id' AND user_id='$user_id'");
+
+			if (db_numrows($res) <1 ) {
+				$GROUPJOINREQUEST_OBJ["_".$group_id."_".$user_id."_"]=false;
+				return false;
+			}
+			$data =& db_fetch_array($res);
+
+		}
+		$grp =& group_get_object($group_id);
+		$GROUPJOINREQUEST_OBJ["_".$group_id."_".$user_id."_"]= new GroupJoinRequest($grp,$user_id,$data);
+
+	}
+
+	return $GROUPJOINREQUEST_OBJ["_".$group_id."_".$user_id."_"];
+}
+*/
+function &get_group_join_requests($Group) {
+	if (!$Group || !is_object($Group) || $Group->isError()) {
+		return false;
+	} else {
+		$res=db_query("SELECT * FROM group_join_request WHERE group_id='".$Group->getID()."'");
+		while ($arr = db_fetch_array($res)) {
+			$reqs[] = new GroupJoinRequest($Group,$arr['user_id'],$arr);
+		}
+		return $reqs;
+	}
+}
+
+class GroupJoinRequest extends Error {
+
+	/**
+	 * Associative array of data from db.
+	 *
+	 * @var  array   $data_array.
+	 */
+	var $data_array;
+
+	var $Group;
+	/**
+	 *  Constructor.
+	 *
+	 *  @param  Group 	The Group object.
+	 *  @param  int 	The user_id.
+	 *  @param  array 	The associative array of data.
+	 *  @return boolean success.
+	 */
+	function GroupJoinRequest($Group=false, $user_id=false, $arr=false) {
+		$this->error(); 
+
+		if (!$Group || !is_object($Group)) {
+			$this->setError('GroupJoinRequest:: No Valid Group Object');
+				return false;
+		}
+		if ($Group->isError()) {
+			$this->setError('GroupJoinRequest:: '.$Group->getErrorMessage());
+				return false;
+		}
+		$this->Group =& $Group;
+		if ($user_id) {
+			if (!$arr || !is_array($arr)) {
+				if (!$this->fetchData($group_id,$user_id)) {
+					return false;
+				}
+			} else {
+				$this->data_array =& $arr;
+				//
+				//      Verify this message truly belongs to this Group
+				//
+				if ($this->data_array['group_id'] != $this->Group->getID()) {
+					$this->setError('group_id in db result does not match Group Object');
+					return false;
+				}
+			} 
+		}
+		return true;
+	}
+
+	/**
+	 * create - create a new GroupJoinRequest in the database.
+	 *
+	 *	@param	int4 user_id.
+	 *	@param	text comments.
+	 *	@param	int4 request_date.
+	 * @return boolean Success.
+	 */
+	function create($user_id,$comments) {
+		global $Language;
+		
+		$v = new Validator();
+		$v->check($user_id, "user_id");
+		//$v->check($comments, "comments");
+		if (!$v->isClean()) {
+			$this->setError($v->formErrorMsg("Must include "));
+			return false;
+		}
+
+		// Check if user is already a member of the project
+		$perm =& $this->Group->getPermission( user_get_object($user_id) );
+		if ($perm && is_object($perm) && $perm->isMember()) {
+			$this->setError($Language->getText('project_joinrequest','already_member'));
+			return false;
+		}
+
+		// Check if user has already submitted a request
+		$sql = "SELECT * FROM group_join_request WHERE group_id='".$this->Group->getID()."' AND user_id='".$user_id."'";
+		$result = db_query($sql);
+		if (db_numrows($result)) {
+			$this->setError($Language->getText('project_joinrequest','already_requested'));
+			return false;
+		}
+
+		db_begin();
+
+		$sql="INSERT INTO group_join_request (group_id,user_id,comments,request_date)
+			VALUES ('".$this->Group->getID()."','".$user_id."',
+			'".htmlspecialchars($comments)."','".time()."')";
+		$result=db_query($sql);
+		if (!$result || db_affected_rows($result) < 1) {
+			$this->setError('GroupJoinRequest::create() Posting Failed '.db_error());
+			db_rollback();
+			return false;
+		} else {
+			if (!$this->fetchData($group_id,$user_id)) {
+				db_rollback();
+				return false;
+			} else {
+				$this->sendJoinNotice();
+				db_commit();
+				return true;
+			}
+		}
+	}
+
+    /**
+	 *  fetchData - re-fetch the data for this GroupJoinRequest from the database.
+	 *
+	 *  @param  int  The group_id.
+	 *  @param  int  The user_id.
+	 *  @return     boolean success.
+	 */
+	function fetchData($group_id,$user_id) {
+	        $res=db_query("SELECT * FROM group_join_request
+	                WHERE
+					user_id='$user_id'
+	        		AND group_id='". $this->Group->getID() ."'");
+	        if (!$res || db_numrows($res) < 1) {
+	                $this->setError('GroupJoinRequest::fetchData() Invalid ID '.db_error());
+	                return false;
+			}
+	        $this->data_array =& db_fetch_array($res);
+	        db_free_result($res);
+	        return true;
+	}
+
+	/**
+	 *      getID - get this GroupJoinRequest ID
+	 *
+	 *      @return int The group_id.
+	 * /
+	function getID() {
+		return $this->data_array['group_id'];
+	}
+
+	/**
+	 *      getGroup - get the group object.
+	 *
+	 *      @return Group The Group.
+	 */
+	function &getGroup() {
+		return $this->Group;
+	}
+
+	/**
+	 *      getUserId - get the field user_id.
+	 *
+	 *      @return int4 The field.
+	 */
+	function getUserId() {
+		return $this->data_array['user_id'];
+	}
+
+	/**
+	 *      getComments - get the field comments.
+	 *
+	 *      @return text The field.
+	 * /
+	function getComments() {
+		return $this->data_array['comments'];
+	}
+
+	/**
+	 *      getRequestDate - get the field request_date.
+	 *
+	 *      @return int4 The field.
+	 */
+	function getRequestDate() {
+		return $this->data_array['request_date'];
+	}
+
+	/**
+	 *	sendJoinNotice() - 
+	 *
+	 *	@return boolean	true/false.
+	 */
+	function sendJoinNotice() {
+		global $Language;
+		$user =& session_get_user();
+		$admins =& $this->Group->getAdmins();
+		for ($i=0; $i<count($admins); $i++) {
+			$emails[]=$admins[$i]->getEmail();
+		}
+		$email=implode($emails,',');
+		$subject = $Language->getText('project_joinrequest','join_notice_subject',array($this->Group->getPublicName()));
+		$comments = util_unconvert_htmlspecialchars($this->data_array["comments"]);
+		$body = $Language->getText('project_joinrequest','join_notice_body',array($user->getRealName(),$GLOBALS['sys_default_domain'],$this->Group->getId(),$comments));
+		$body = str_replace("\\n","\n",$body);
+
+		return util_send_message($email,$subject,$body);
+	}
+
+	/**
+	 *	reject()
+	 *
+	 *	@return	boolean	success.
+	 */
+	function reject() {
+		global $Language;
+		$user =& user_get_object($this->getUserId());
+		$subject = $Language->getText('project_joinrequest','join_notice_subject',array($this->Group->getPublicName()));
+		$body = $Language->getText('project_joinrequest','denied',array($this->Group->getPublicName()));
+		util_send_message($user->getEmail(),$subject,$body);
+		return $this->delete(1);
+	}
+
+	/**
+	 *	delete() - delete this row from the database.
+	 *
+	 *	@param	boolean	I'm Sure.
+	 *	@return	boolean	true/false.
+	 */
+	function delete($sure) {
+		if (!$sure) {
+			$this->setError('Must be sure before deleting');
+			return false;
+		}
+		$perm =& $this->Group->getPermission( session_get_user() );
+		if (!$perm || !is_object($perm)) {
+			$this->setPermissionDeniedError();
+			return false;
+		} elseif ($perm->isError()) {
+			$this->setPermissionDeniedError();
+			return false;
+		} elseif (!$perm->isAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		} else {
+			$res=db_query("DELETE FROM group_join_request WHERE 
+				group_id='".$this->Group->getID()."' 
+				AND user_id='".$this->getUserId()."'");
+			if (!$res || db_affected_rows($res) < 1) {
+				$this->setError('Could Not Delete: '.db_error());
+			} else {
+				return true;
+			}
+		}
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/include/Jabber.class
===================================================================
--- trunk/gforge_base/gforge/common/include/Jabber.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/Jabber.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,1533 @@
+<?php
+
+/***************************************************************************
+
+	Class.Jabber.PHP v0.1.3.1
+	(c) 2002 Carlo "Gossip" Zottmann
+	http://phpjabber.g-blog.net *** gossip at jabber.g-blog.net
+
+	The FULL documentation and examples for this software can be found at
+	http://phpjabber.g-blog.net (not many doc comments in here, sorry)
+	
+	last modified: 2002-10-09 21:57:20
+
+	NOTE:
+	If you want to write addons or extensions, please follow the coding style
+	recommendations @ http://www.phpbuilder.net/columns/tim20010101.php3
+
+ ***************************************************************************/
+
+/***************************************************************************
+ *
+ * The Notice below must appear in each file of the Source Code of any copy
+ * you distribute of the Licensed Product or any Modifications thereto.
+ * Contributors to any Modifications may add their own copyright notices to
+ * identify their own contributions.
+ * 
+ * License
+ * 
+ * The contents of this file are subject to the Jabber Open Source License
+ * Version 1.0 (the "License").  You may not copy or use this file, in either
+ * source code or executable form, except in compliance with the License.  You
+ * may obtain a copy of the License at http://www.jabber.com/license/ or at
+ * http://www.opensource.org/.  
+ * 
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ * 
+ * Copyrights
+ * 
+ * Portions created by or assigned to Jabber.com, Inc. are 
+ * Copyright (c) 2000 Jabber.com, Inc.  All Rights Reserved.  Contact
+ * information for Jabber.com, Inc. is available at http://www.jabber.com/.
+ * 
+ * Portions Copyright (c) 2002-present Carlo Zottmann,
+ * http://phpjabber.g-blog.net
+ *  
+ * Other portions copyright their respective owners.
+ * 
+ * Acknowledgements
+ * 
+ * Special thanks to the Jabber Open Source Contributors for their
+ * suggestions and support of Jabber.
+ * 
+ ***************************************************************************/
+
+/*
+	Jabber::Connect() 
+	Jabber::Disconnect() 
+	Jabber::SendAuth() 
+	Jabber::AccountRegistration($reg_email {string}, $reg_name {string})
+ 
+	Jabber::Listen() 
+	Jabber::SendPacket($xml {string})
+ 
+	Jabber::RosterUpdate() 
+	Jabber::RosterAddUser($jid {string}, $id {string}, $name {string}) 
+	Jabber::RosterRemoveUser($jid {string}, $id {string})
+ 
+	Jabber::Subscribe($jid {string}) 
+	Jabber::Unsubscribe($jid {string})
+ 
+	Jabber::CallHandler($message {array}) 
+	Jabber::CruiseControl([$seconds {number}])
+ 
+	Jabber::SubscriptionApproveRequest($to {string}) 
+	Jabber::SubscriptionDenyRequest($to {string})
+ 
+	Jabber::GetFirstFromQueue() 
+	Jabber::GetFromQueueById($packet_type {string}, $id {string})
+ 
+	Jabber::SendMessage($to {string}, $id {number}, $type {string}, $content {array}[, $payload {array}])
+ 	Jabber::SendIq($to {string}, $type {string}, $id {string}, $xmlns {string}[, $payload {string}])
+	Jabber::SendPresence($type {string}[, $to {string}[, $status {string}[, $show {string}[, $priority {number}]]]])
+ 
+	Jabber::SendError($to {string}, $id {string}, $error_number {number}[, $error_message {string}]) 
+
+	Jabber::GetInfoFromMessageFrom($message {array}) 
+	Jabber::GetInfoFromMessageType($message {array}) 
+	Jabber::GetInfoFromMessageId($message {array}) 
+	Jabber::GetInfoFromMessageThread($message {array}) 
+	Jabber::GetInfoFromMessageSubject($message {array}) 
+	Jabber::GetInfoFromMessageBody($message {array}) 
+	Jabber::GetInfoFromMessageError($message {array})
+ 
+	Jabber::GetInfoFromIqFrom($message {array}) 
+	Jabber::GetInfoFromIqType($message {array}) 
+	Jabber::GetInfoFromIqId($message {array}) 
+	Jabber::GetInfoFromIqKey($message {array})
+ 
+	Jabber::GetInfoFromPresenceFrom($message {array}) 
+	Jabber::GetInfoFromPresenceType($message {array}) 
+	Jabber::GetInfoFromPresenceStatus($message {array}) 
+	Jabber::GetInfoFromPresenceShow($message {array}) 
+	Jabber::GetInfoFromPresencePriority($message {array})
+
+
+	MakeXML::AddPacketDetails($string {string}[, $value {string/number}])
+	MakeXML::BuildPacket([$array {array}])
+*/
+
+
+
+class Jabber
+{
+	var $server;
+	var $port;
+	var $username;
+	var $password;
+	var $resource;
+	var $jid;
+
+	var $connection;
+
+	var $stream_id;
+	var $roster;
+
+	var $enable_logging;
+	var $logfile;
+
+	var $iq_sleep_timer;
+
+	var $packet_queue;
+	var $subscription_queue;
+	
+	var $iq_version_name;
+	var $iq_version_os;
+	var $iq_version_version;
+
+	var $error_codes;
+	
+	var $CONNECTOR;
+
+
+
+	function Jabber()
+	{
+		$this->server				= $GLOBALS['sys_jabber_server'];
+		$this->port					= $GLOBALS['sys_jabber_port'];
+
+		$this->username				= $GLOBALS['sys_jabber_user'];
+		$this->password				= $GLOBALS['sys_jabber_pass'];
+		$this->resource				= 'home';
+		
+		$this->enable_logging		= FALSE;
+		$this->logfile				= array();
+
+		$this->packet_queue			= array();
+		$this->subscription_queue	= array();
+
+		$this->iq_sleep_timer		= 1;
+
+		$this->iq_version_name		= "Class.Jabber.PHP by Carlo 'Gossip' Zottmann, gossip at jabber.g-blog.net";
+		$this->iq_version_version	= "0.1.3";
+		$this->iq_version_os		= $_SERVER["SERVER_SOFTWARE"];
+
+		$this->connection_class		= "CJP_StandardConnector";
+
+		$this->error_codes			= array(400 => "Bad Request",
+											401 => "Unauthorized",
+											402 => "Payment Required",
+											403 => "Forbidden",
+											404 => "Not Found",
+											405 => "Not Allowed",
+											406 => "Not Acceptable",
+											407 => "Registration Required",
+											408 => "Request Timeout",
+											409 => "Conflict",
+											500 => "Internal Server Error",
+											501 => "Not Implemented",
+											502 => "Remove Server Error",
+											503 => "Service Unavailable",
+											504 => "Remove Server Timeout",
+											510 => "Disconnected");
+	}
+
+
+
+	function Connect()
+	{
+		$this->CONNECTOR = new $this->connection_class;
+		$this->connection = $this->CONNECTOR->OpenSocket($this->server, $this->port);
+
+		if ($this->connection) {
+
+	        socket_set_blocking($this->connection, 0);
+			socket_set_timeout($this->connection, 31536000);
+
+			$this->SendPacket("<?xml version='1.0' encoding='UTF-8' ?>\n");
+			$this->SendPacket("<stream:stream to='" . $this->server . "' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>\n");
+
+			sleep(2);
+
+			if ($this->_check_connected()) {
+
+				return TRUE;
+
+			} else {
+
+				if ($this->enable_logging) {
+					$this->logfile[] = "<strong>Error:</strong> Connect() #1";
+				}
+
+				return FALSE;
+			}
+
+		} else {
+
+			if ($this->enable_logging) {
+				$this->logfile[] = "<strong>Error:</strong> Connect() #2";
+			}
+
+			return FALSE;
+		}
+	}
+
+
+
+	function Disconnect()
+	{
+		$this->SendPacket("</stream:stream>");
+		$this->CONNECTOR->CloseSocket($this->connection);
+		
+		if ($this->enable_logging) {
+			echo "<h2>logging enabled, logged events below:</h2>\n";
+			echo (count($this->logfile) > 0) ? implode("<br /><br />\n", $this->logfile) : "No logged events.";
+		}
+	}
+
+
+
+	function SendAuth()
+	{
+		// Currently, we only support plaintext authentication. This ain't
+		// perfect, but it works. I'll add <digest/> support later on...
+
+		if ($this->resource) {
+			$this->jid = $this->username . "@" . $this->server . "/" . $this->resource;
+		} else {
+			$this->jid = $this->username . "@" . $this->server;
+		}
+
+		$auth_id = "auth_" . time();
+
+		$payload = "<username>" . $this->username . "</username>
+					<password>" . $this->password . "</password>
+					<resource>" . $this->resource . "</resource>";
+
+		$packet = $this->SendIq(NULL, "set", $auth_id, "jabber:iq:auth", $payload);
+
+		if ($this->GetInfoFromIqType($packet) == "result" && $this->GetInfoFromIqId($packet) == $auth_id) {
+
+			return TRUE;
+
+		} else {
+
+			if ($this->enable_logging) {
+				$this->logfile[] = "<strong>Error:</strong> SendAuth() #1";
+			}
+
+			return FALSE;
+		}
+	}
+
+
+
+	function AccountRegistration($reg_email = NULL, $reg_name = NULL)
+	{
+		$packet = $this->SendIq($this->server, "get", "reg_01", "jabber:iq:register");
+		
+		if ($packet) {
+	
+			$key = $this->GetInfoFromIqKey($packet);	// just in case a key was passed back from the server
+			unset($packet);
+		
+			$payload = "<username>" . $this->username . "</username>
+						<password>" . $this->password . "</password>
+						<email>$reg_email</email>
+						<name>$reg_name</name>\n";
+			$payload .= ($key) ? "<key>$key</key>\n" : "";
+		
+			$packet = $this->SendIq($this->server, "set", "reg_01", "jabber:iq:register", $payload);
+		
+			if ($this->GetInfoFromIqType($packet) == "result") {
+			
+				if (isset($packet["iq"]["#"]["query"][0]["#"]["registered"][0]["#"])) {
+					$return_code = 1;
+				} else {
+					$return_code = 2;
+				}
+		
+				if ($this->resource) {
+					$this->jid = $this->username . "@" . $this->server . "/" . $this->resource;
+				} else {
+					$this->jid = $this->username . "@" . $this->server;
+				}
+
+			} elseif ($this->GetInfoFromIqType($packet) == "error") {
+		
+				if (isset($packet["iq"]["#"]["error"][0]["#"])) {
+					$return_code = "Error " . $packet["iq"]["#"]["error"][0]["@"]["code"] . ": " . $packet["iq"]["#"]["error"][0]["#"];
+				}
+			}
+	
+			return $return_code;
+	
+		} else {
+		
+			return 3;
+	
+		}
+	}
+
+
+
+	function SendPacket($xml)
+	{
+		$xml = trim($xml);
+
+		if ($this->CONNECTOR->WriteToSocket($this->connection, $xml)) {
+
+			if ($this->enable_logging) {
+				$this->logfile[] = "<strong>SEND:</strong> " . nl2br(htmlspecialchars($xml));
+			}
+
+			return TRUE;
+
+		} else {
+
+			if ($this->enable_logging) {
+				$this->logfile[] = "<strong>Error:</strong> SendPacket() #1";
+			}
+
+			return FALSE;
+		}
+	}
+
+
+
+	function Listen()
+	{
+		unset($incoming);
+
+		while ($line = $this->CONNECTOR->ReadFromSocket($this->connection, 4096)) {
+			$incoming .= $line;
+		}
+		
+		$incoming = trim($incoming);
+
+		if ($this->enable_logging && $incoming != "") {
+			$this->logfile[] = "<strong>RECV:</strong> " . nl2br(htmlspecialchars($incoming));
+		}
+
+		if ($incoming != "") {
+			$temp = $this->_split_incoming($incoming);
+
+			for ($a = 0; $a < count($temp); $a++) {
+				$this->packet_queue[] = $this->xmlize($temp[$a]);
+			}
+		}
+
+		return TRUE;
+	}
+
+
+
+	function StripJID($jid = NULL)
+	{
+		preg_match("/(.*)\/(.*)/Ui", $jid, $temp);
+		return ($temp[1] != "") ? $temp[1] : $jid;
+	}
+
+
+
+	function SendMessage($to, $type = "normal", $id = NULL, $content = NULL, $payload = NULL)
+	{
+		if ($to && is_array($content)) {
+		
+			if (!$id) { $id = $type . "_" . time(); }
+
+			$content = $this->_array_htmlspecialchars($content);
+
+			$xml = "<message to='$to' type='$type' id='$id'>\n";
+
+			if ($content["thread"]) {
+				$xml .= "<thread>" . $content["thread"] . "</thread>\n";
+			}
+			if ($content['subject']) { 
+				$xml .= "<subject>" . $content['subject'] . "</subject>\n"; 
+			}
+			
+			$xml .= "<body>" . $content["body"] . "</body>\n";
+			$xml .= $payload;
+			$xml .= "</message>\n";
+
+
+			if ($this->SendPacket($xml)) {
+
+				return TRUE;
+
+			} else {
+
+				if ($this->enable_logging) {
+					$this->logfile[] = "<strong>Error:</strong> SendMessage() #1";
+				}
+
+				return FALSE;
+			}
+
+		} else {
+		
+			if ($this->enable_logging) {
+				$this->logfile[] = "<strong>Error:</strong> SendMessage() #2";
+			}
+
+			return FALSE;
+		}
+	}
+
+
+
+	function SendPresence($type = NULL, $to = NULL, $status = NULL, $show = NULL, $priority = NULL)
+	{
+		$xml = "<presence";
+		$xml .= ($to) ? " to='$to'" : "";
+		$xml .= ($type) ? " type='$type'" : "";
+		$xml .= ($status || $show || $priority) ? ">\n" : " />\n";
+
+		$xml .= ($status) ? "	<status>$status</status>\n" : "";
+		$xml .= ($show) ? "	<show>$show</show>\n" : "";
+		$xml .= ($priority) ? "	<priority>$priority</priority>\n" : "";
+
+		$xml .= ($status || $show || $priority) ? "</presence>\n" : "";
+
+		if ($this->SendPacket($xml)) {
+
+			return TRUE;
+
+		} else {
+
+			if ($this->enable_logging) {
+				$this->logfile[] = "<strong>Error:</strong> SendPresence() #1";
+			}
+
+			return FALSE;
+		}
+	}
+
+
+
+	function SendError($to, $id = NULL, $error_number, $error_message = NULL)
+	{
+		$xml = "<iq type='error' to='$to'";
+		$xml .= ($id) ? " id='$id'" : "";
+		$xml .= ">\n";
+		$xml .= "	<error code='$error_number'>";
+		$xml .= ($error_message) ? $error_message : $this->error_codes[$error_number];
+		$xml .= "</error>\n";
+		$xml .= "</iq>";
+
+		$this->SendPacket($xml);
+	}
+
+
+
+	function RosterUpdate()
+	{
+		$roster_request_id = "roster_" . time();
+
+		$incoming_array = $this->SendIq(NULL, "get", $roster_request_id, "jabber:iq:roster");
+
+		if (is_array($incoming_array)) {
+			
+			if ($incoming_array["iq"]["@"]["type"] == "result"
+				&& $incoming_array["iq"]["@"]["id"] == $roster_request_id
+				&& $incoming_array["iq"]["#"]["query"]["0"]["@"]["xmlns"] == "jabber:iq:roster")
+			{
+
+				$number_of_contacts = count($incoming_array["iq"]["#"]["query"][0]["#"]["item"]);
+				$this->roster = array();
+
+				for ($a = 0; $a < $number_of_contacts; $a++) {
+
+					$this->roster[$a] = array(	"jid"			=> $incoming_array["iq"]["#"]["query"][0]["#"]["item"][$a]["@"]["jid"],
+												"name"			=> $incoming_array["iq"]["#"]["query"][0]["#"]["item"][$a]["@"]["name"],
+												"subscription"	=> $incoming_array["iq"]["#"]["query"][0]["#"]["item"][$a]["@"]["subscription"],
+												"group"			=> $incoming_array["iq"]["#"]["query"][0]["#"]["item"][$a]["#"]["group"][0]["#"]
+											);
+				}
+
+				return TRUE;
+
+			} else {
+
+				if ($this->enable_logging) {
+					$this->logfile[] = "<strong>Error:</strong> RosterUpdate() #1";
+				}
+
+				return FALSE;
+			}
+
+		} else {
+
+			if ($this->enable_logging) {
+				$this->logfile[] = "<strong>Error:</strong> RosterUpdate() #2";
+			}
+
+			return FALSE;
+		}
+	}
+
+
+
+	function RosterAddUser($jid = NULL, $id = NULL, $name = NULL)
+	{
+		$id = ($id) ? $id : "adduser_" . time();
+
+		if ($jid) {
+	
+			$payload = "		<item jid='$jid'";
+			$payload .= ($name) ? " name='" . htmlspecialchars($name) . "'" : "";
+			$payload .= "/>\n";
+
+			$packet = $this->SendIq(NULL, "set", $id, "jabber:iq:roster", $payload);
+
+			if ($this->GetInfoFromIqType($packet) == "result") {
+
+				$this->RosterUpdate();
+				return TRUE;
+			
+			} else {
+
+				if ($this->enable_logging) {
+					$this->logfile[] = "<strong>Error:</strong> RosterAddUser() #2";
+				}
+
+				return FALSE;
+			}
+
+		} else {
+
+			if ($this->enable_logging) {
+				$this->logfile[] = "<strong>Error:</strong> RosterAddUser() #1";
+			}
+
+			return FALSE;
+		}
+	}
+
+
+
+	function RosterRemoveUser($jid = NULL, $id = NULL)
+	{
+		if ($jid && $id) {
+
+			$packet = $this->SendIq(NULL, "set", $id, "jabber:iq:roster", "<item jid='$jid' subscription='remove'/>");
+
+			if ($this->GetInfoFromIqType($packet) == "result") {
+
+				$this->RosterUpdate();
+				return TRUE;
+			
+			} else {
+
+				if ($this->enable_logging) {
+					$this->logfile[] = "<strong>Error:</strong> RosterRemoveUser() #2";
+				}
+
+				return FALSE;
+			}
+
+		} else {
+
+			if ($this->enable_logging) {
+				$this->logfile[] = "<strong>Error:</strong> RosterRemoveUser() #1";
+			}
+
+			return FALSE;
+		}
+	}
+
+
+
+	function GetFirstFromQueue()
+	{
+		reset($this->packet_queue);
+		list($key, $value) = each($this->packet_queue);
+		unset($this->packet_queue[$key]);
+
+		return (is_array($value)) ? $value : FALSE;
+	}
+
+
+
+	function GetFromQueueById($packet_type, $id)
+	{
+		$found_message = FALSE;
+
+		foreach ($this->packet_queue as $key => $value) {
+
+			if ($value["$packet_type"]["@"]["id"] == $id) {
+
+				$found_message = $value;
+				unset($this->packet_queue[$key]);
+
+				break;
+			}
+		}
+
+		return (is_array($found_message)) ? $found_message : FALSE;
+	}
+
+
+
+	function CallHandler($packet = NULL)
+	{
+		$packet_type	= $this->_get_packet_type($packet);
+
+		if ($packet_type == "message") {
+		
+			$type		= $packet["message"]["@"]["type"];
+			$type		= ($type != "") ? $type : "normal";
+			$funcmeth	= "Handler_message_$type";
+
+		} elseif ($packet_type == "iq") {
+		
+			$this->TraverseXMLize($packet);
+
+			$namespace	= $packet["iq"]["#"]["query"][0]["@"]["xmlns"];
+			$namespace	= str_replace(":", "_", $namespace);
+			$funcmeth	= "Handler_iq_$namespace";
+
+		} elseif ($packet_type == "presence") {
+
+			$type		= $packet["presence"]["@"]["type"];
+			$type		= ($type != "") ? $type : "available";
+			$funcmeth	= "Handler_presence_$type";
+
+		}
+
+
+		if ($funcmeth != "") {
+
+			if (function_exists($funcmeth)) {
+
+				call_user_func($funcmeth, $packet);
+
+			} elseif(method_exists($this, $funcmeth)) {
+
+				call_user_func(array(&$this, $funcmeth), $packet);
+
+			} elseif ($this->enable_logging) {
+
+				$this->Handler_NOT_IMPLEMENTED($packet);
+				$this->logfile[] = "<strong>Error:</strong> CallHandler() #1 - neither method nor function $funcmeth() available";
+
+			}
+		}
+	}
+
+
+
+	function CruiseControl($seconds = -1)
+	{
+		$count = 0;
+
+		while ($count != $seconds) {
+			$this->Listen();
+
+			do {
+				$packet = $this->GetFirstFromQueue();
+				$this->CallHandler($packet);
+			} while (count($this->packet_queue) > 1);
+
+			$count++;
+			sleep(1);
+		}
+		
+		return TRUE;
+	}
+
+
+
+	function SubscriptionAcceptRequest($to = NULL)
+	{
+		return ($to) ? $this->SendPresence("subscribed", $to) : FALSE;
+	}
+
+
+
+	function SubscriptionDenyRequest($to = NULL)
+	{
+		return ($to) ? /* still needs to be done */ TRUE : FALSE;
+	}
+
+
+
+	function Subscribe($to = NULL)
+	{
+		return ($to) ? $this->SendPresence("subscribe", $to) : FALSE;
+	}
+
+
+
+	function Unsubscribe($to = NULL)
+	{
+		return ($to) ? $this->SendPresence("unsubscribe", $to) : FALSE;
+	}
+
+
+
+	function SendIq($to = NULL, $type = "get", $id = NULL, $xmlns = NULL, $payload = NULL)
+	{
+		if (!preg_match("/^(get|set|result|error)$/", $type)) {
+			unset($type);
+
+			if ($this->enable_logging) {
+				$this->logfile[] = "<strong>Error:</strong> SendIq() #2 - type must be 'get', 'set', 'result' or 'error'";
+			}
+			
+			return FALSE;
+
+		} elseif ($id && $xmlns) {
+
+			$xml = "<iq type='$type' id='$id'";
+			$xml .= ($to) ? " to='$to'" : "";
+			$xml .= ">
+						<query xmlns='$xmlns'>
+							$payload
+						</query>
+					</iq>";
+
+			$this->SendPacket($xml);
+			sleep($this->iq_sleep_timer);
+			$this->Listen();
+			
+			return (preg_match("/^(get|set)$/", $type)) ? $this->GetFromQueueById("iq", $id) : TRUE;
+
+		} else {
+
+			if ($this->enable_logging) {
+				$this->logfile[] = "<strong>Error:</strong> SendIq() #1 - to, id and xmlns are mandatory";
+			}
+
+			return FALSE;
+		}
+	}
+
+
+
+	// ======================================================================
+	// internal methods
+	// ======================================================================
+
+
+
+	function _listen_incoming()
+	{
+		unset($incoming);
+
+		while ($line = $this->CONNECTOR->ReadFromSocket($this->connection, 4096)) {
+			$incoming .= $line;
+		}
+		
+		$incoming = trim($incoming);
+
+		if ($this->enable_logging && $incoming != "") {
+			$this->logfile[] = "<strong>RECV:</strong> " . nl2br(htmlspecialchars($incoming));
+		}
+
+		return $this->xmlize($incoming);
+	}
+
+
+
+	function _check_connected()
+	{
+		$incoming_array = $this->_listen_incoming();
+		
+		if (is_array($incoming_array)) {
+			
+			if ($incoming_array["stream:stream"]["@"]["from"] == $this->server
+				&& $incoming_array["stream:stream"]["@"]["xmlns"] == "jabber:client"
+				&& $incoming_array["stream:stream"]["@"]["xmlns:stream"] == "http://etherx.jabber.org/streams")
+			{
+
+				$this->stream_id = $incoming_array["stream:stream"]["@"]["id"];
+
+				return TRUE;
+
+			} else {
+
+				if ($this->enable_logging) {
+					$this->logfile[] = "<strong>Error:</strong> _check_connected() #1";
+				}
+
+				return FALSE;
+			}
+
+		} else {
+
+			if ($this->enable_logging) {
+				$this->logfile[] = "<strong>Error:</strong> _check_connected() #2";
+			}
+
+			return FALSE;
+		}
+	}
+
+
+
+	function _get_packet_type($packet = NULL)
+	{
+		if (is_array($packet)) {
+			reset($packet);
+			$packet_type = key($packet);
+		}
+
+		return ($packet_type) ? $packet_type : FALSE;
+	}
+
+
+
+	function _split_incoming($incoming)
+	{
+		$temp = preg_split("/<(message|iq|presence|stream)/", $incoming, -1, PREG_SPLIT_DELIM_CAPTURE);
+		$array = array();
+
+		for ($a = 1; $a < count($temp); $a = $a + 2) {
+			$array[] = "<" . $temp[$a] . $temp[($a + 1)];
+		}
+
+		return $array;
+	}
+
+
+
+	// _array_htmlspecialchars()
+	// applies htmlspecialchars() to all values in an array
+
+	function _array_htmlspecialchars($array)
+	{
+		if (is_array($array)) {
+			foreach ($array as $k => $v) {
+				if (is_array($v)) {
+					$v = $this->_array_htmlspecialchars($v);
+				} else {
+					$v = htmlspecialchars($v);
+				}
+			}
+		}
+		
+		return $array;
+	}
+
+
+
+	// ======================================================================
+	// <message/> parsers
+	// ======================================================================
+
+
+
+	function GetInfoFromMessageFrom($packet = NULL)
+	{
+		return (is_array($packet)) ? $packet["message"]["@"]["from"] : FALSE;
+	}
+
+
+
+	function GetInfoFromMessageType($packet = NULL)
+	{
+		return (is_array($packet)) ? $packet["message"]["@"]["type"] : FALSE;
+	}
+
+
+
+	function GetInfoFromMessageId($packet = NULL)
+	{
+		return (is_array($packet)) ? $packet["message"]["@"]["id"] : FALSE;
+	}
+
+
+
+	function GetInfoFromMessageThread($packet = NULL)
+	{
+		return (is_array($packet)) ? $packet["message"]["#"]["thread"][0]["#"] : FALSE;
+	}
+
+
+
+	function GetInfoFromMessageSubject($packet = NULL)
+	{
+		return (is_array($packet)) ? $packet["message"]["#"]["subject"][0]["#"] : FALSE;
+	}
+
+
+
+	function GetInfoFromMessageBody($packet = NULL)
+	{
+		return (is_array($packet)) ? $packet["message"]["#"]["body"][0]["#"] : FALSE;
+	}
+
+
+
+	function GetInfoFromMessageError($packet = NULL)
+	{
+		$error = preg_replace("/^\/$/", "", ($packet["message"]["#"]["error"][0]["@"]["code"] . "/" . $packet["message"]["#"]["error"][0]["#"]));
+		return (is_array($packet)) ? $error : FALSE;
+	}
+
+
+
+	// ======================================================================
+	// <iq/> parsers
+	// ======================================================================
+
+
+
+	function GetInfoFromIqFrom($packet = NULL)
+	{
+		return (is_array($packet)) ? $packet["iq"]["@"]["from"] : FALSE;
+	}
+
+
+
+	function GetInfoFromIqType($packet = NULL)
+	{
+		return (is_array($packet)) ? $packet["iq"]["@"]["type"] : FALSE;
+	}
+
+
+
+	function GetInfoFromIqId($packet = NULL)
+	{
+		return (is_array($packet)) ? $packet["iq"]["@"]["id"] : FALSE;
+	}
+
+
+
+	function GetInfoFromIqKey($packet = NULL)
+	{
+		return (is_array($packet)) ? $packet["iq"]["#"]["query"][0]["#"]["key"][0]["#"] : FALSE;
+	}
+
+
+
+	// ======================================================================
+	// <presence/> parsers
+	// ======================================================================
+
+
+
+	function GetInfoFromPresenceFrom($packet = NULL)
+	{
+		return (is_array($packet)) ? $packet["presence"]["@"]["from"] : FALSE;
+	}
+
+
+
+	function GetInfoFromPresenceType($packet = NULL)
+	{
+		return (is_array($packet)) ? $packet["presence"]["@"]["type"] : FALSE;
+	}
+
+
+
+	function GetInfoFromPresenceStatus($packet = NULL)
+	{
+		return (is_array($packet)) ? $packet["presence"]["#"]["status"][0]["#"] : FALSE;
+	}
+
+
+
+	function GetInfoFromPresenceShow($packet = NULL)
+	{
+		return (is_array($packet)) ? $packet["presence"]["#"]["show"][0]["#"] : FALSE;
+	}
+
+
+
+	function GetInfoFromPresencePriority($packet = NULL)
+	{
+		return (is_array($packet)) ? $packet["presence"]["#"]["priority"][0]["#"] : FALSE;
+	}
+
+
+
+	// ======================================================================
+	// <message/> handlers
+	// ======================================================================
+
+
+
+	function Handler_message_normal($packet)
+	{
+		$from = $packet["message"]["@"]["from"];
+		$this->logfile[] = "<strong>message</strong> (type normal) from $from";
+	}
+
+
+
+	function Handler_message_chat($packet)
+	{
+		$from = $packet["message"]["@"]["from"];
+		$this->logfile[] = "<strong>message</strong> (type chat) from $from";
+	}
+
+
+
+	function Handler_message_groupchat($packet)
+	{
+		$from = $packet["message"]["@"]["from"];
+		$this->logfile[] = "<strong>message</strong> (type groupchat) from $from";
+	}
+
+
+
+	function Handler_message_headline($packet)
+	{
+		$from = $packet["message"]["@"]["from"];
+		$this->logfile[] = "<strong>message</strong> (type headline) from $from";
+	}
+
+
+
+	function Handler_message_error($packet)
+	{
+		$from = $packet["message"]["@"]["from"];
+		$this->logfile[] = "<strong>message</strong> (type error) from $from";
+	}
+
+
+
+	// ======================================================================
+	// <iq/> handlers
+	// ======================================================================
+
+
+
+	// application version updates
+    function Handler_iq_jabber_iq_autoupdate($packet)
+	{
+		$from	= $this->GetInfoFromIqFrom($packet);
+		$id		= $this->GetInfoFromIqId($packet);
+		
+		$this->SendError($from, $id, 501);
+		$this->logfile[] = "<strong>jabber:iq:autoupdate</strong> from $from";
+	}
+
+
+
+	// interactive server component properties
+    function Handler_iq_jabber_iq_agent($packet)
+	{
+		$from	= $this->GetInfoFromIqFrom($packet);
+		$id		= $this->GetInfoFromIqId($packet);
+		
+		$this->SendError($from, $id, 501);
+		$this->logfile[] = "<strong>jabber:iq:agent</strong> from $from";
+	}
+
+
+
+	// method to query interactive server components
+    function Handler_iq_jabber_iq_agents($packet)
+	{
+		$from	= $this->GetInfoFromIqFrom($packet);
+		$id		= $this->GetInfoFromIqId($packet);
+		
+		$this->SendError($from, $id, 501);
+		$this->logfile[] = "<strong>jabber:iq:agents</strong> from $from";
+	}
+
+
+
+	// simple client authentication
+	function Handler_iq_jabber_iq_auth($packet)
+	{
+		$from	= $this->GetInfoFromIqFrom($packet);
+		$id		= $this->GetInfoFromIqId($packet);
+		
+		$this->SendError($from, $id, 501);
+		$this->logfile[] = "<strong>jabber:iq:auth</strong> from $from";
+	}
+
+
+
+	// out of band data
+	function Handler_iq_jabber_iq_oob($packet)
+	{
+		$from	= $this->GetInfoFromIqFrom($packet);
+		$id		= $this->GetInfoFromIqId($packet);
+		
+		$this->SendError($from, $id, 501);
+		$this->logfile[] = "<strong>jabber:iq:oob</strong> from $from";
+	}
+
+
+
+	// method to store private data on the server
+	function Handler_iq_jabber_iq_private($packet)
+	{
+		$from	= $this->GetInfoFromIqFrom($packet);
+		$id		= $this->GetInfoFromIqId($packet);
+		
+		$this->SendError($from, $id, 501);
+		$this->logfile[] = "<strong>jabber:iq:private</strong> from $from";
+	}
+
+
+
+	// method for interactive registration
+	function Handler_iq_jabber_iq_register($packet)
+	{
+		$from	= $this->GetInfoFromIqFrom($packet);
+		$id		= $this->GetInfoFromIqId($packet);
+		
+		$this->SendError($from, $id, 501);
+		$this->logfile[] = "<strong>jabber:iq:register</strong> from $from";
+	}
+
+
+
+	// client roster management
+	function Handler_iq_jabber_iq_roster($packet)
+	{
+		$from	= $this->GetInfoFromIqFrom($packet);
+		$id		= $this->GetInfoFromIqId($packet);
+		
+		$this->SendError($from, $id, 501);
+		$this->logfile[] = "<strong>jabber:iq:roster</strong> from $from";
+	}
+
+
+
+	// method for searching a user database
+	function Handler_iq_jabber_iq_search($packet)
+	{
+		$from	= $this->GetInfoFromIqFrom($packet);
+		$id		= $this->GetInfoFromIqId($packet);
+		
+		$this->SendError($from, $id, 501);
+		$this->logfile[] = "<strong>jabber:iq:search</strong> from $from";
+	}
+
+
+
+	// method for requesting the current time
+	function Handler_iq_jabber_iq_time($packet)
+	{
+		$type	= $this->GetInfoFromIqType($packet);
+		$from	= $this->GetInfoFromIqFrom($packet);
+		$id		= $this->GetInfoFromIqId($packet);
+		$id		= ($id != "") ? $id : "time_" . time();
+
+		if ($type == "get") {
+
+			$payload = "<utc>" . gmdate("Ydm\TH:i:s") . "</utc>
+						<tz>" . date("T") . "</tz>
+						<display>" . date("Y/d/m h:i:s A") . "</display>";
+	
+			$this->SendIq($from, "result", $id, "jabber:iq:time", $payload);
+		}
+
+		$this->logfile[] = "<strong>jabber:iq:time</strong> (type $type) from $from";
+	}
+
+
+
+	// method for requesting version
+	function Handler_iq_jabber_iq_version($packet)
+	{
+		$type	= $this->GetInfoFromIqType($packet);
+		$from	= $this->GetInfoFromIqFrom($packet);
+		$id		= $this->GetInfoFromIqId($packet);
+		$id		= ($id != "") ? $id : "version_" . time();
+
+		if ($type == "get") {
+
+			$payload = "<name>" . $this->iq_version_name . "</name>
+						<os>" . $this->iq_version_os . "</os>
+						<version>" . $this->iq_version_version . "</version>";
+
+			$this->SendIq($from, "result", $id, "jabber:iq:version", $payload);
+		}
+
+		$this->logfile[] = "<strong>jabber:iq:version</strong> (type $type) from $from";
+	}
+
+
+
+	// ======================================================================
+	// <presence/> handlers
+	// ======================================================================
+
+
+
+    function Handler_presence_available($packet)
+	{
+		$from = $this->GetInfoFromPresenceFrom($packet);
+
+		$show_status = $this->GetInfoFromPresenceStatus($packet) . " / " . $this->GetInfoFromPresenceShow($packet);
+		$show_status = ($show_status != " / ") ? " ($addendum)" : "";
+
+		$this->logfile[] = "<strong>Presence:</strong> (type: available) - $from is available $show_status";
+	}
+
+
+
+	function Handler_presence_unavailable($packet)
+	{
+		$from = $this->GetInfoFromPresenceFrom($packet);
+
+		$show_status = $this->GetInfoFromPresenceStatus($packet) . " / " . $this->GetInfoFromPresenceShow($packet);
+		$show_status = ($show_status != " / ") ? " ($addendum)" : "";
+
+		$this->logfile[] = "<strong>Presence:</strong> (type: unavailable) - $from is unavailable $show_status";
+	}
+
+
+
+    function Handler_presence_subscribe($packet)
+	{
+		$from = $this->GetInfoFromPresenceFrom($packet);
+		$this->subscription_queue[] = $from;
+		$this->RosterUpdate();
+
+		$this->logfile[] = "<strong>Presence:</strong> (type: subscribe) - Subscription request from $from, was added to \$this->subscription_queue, roster updated";
+	}
+
+
+
+    function Handler_presence_subscribed($packet)
+	{
+		$from = $this->GetInfoFromPresenceFrom($packet);
+		$this->RosterUpdate();
+
+		$this->logfile[] = "<strong>Presence:</strong> (type: subscribed) - Subscription allowed by $from, roster updated";
+	}
+
+
+
+	function Handler_presence_unsubscribe($packet)
+	{
+		$from = $this->GetInfoFromPresenceFrom($packet);
+		$this->SendPresence("unsubscribed", $from);
+		$this->RosterUpdate();
+
+		$this->logfile[] = "<strong>Presence:</strong> (type: unsubscribe) - Request to unsubscribe from $from, was automatically approved, roster updated";
+	}
+
+
+
+	function Handler_presence_unsubscribed($packet)
+	{
+		$from = $this->GetInfoFromPresenceFrom($packet);
+		$this->RosterUpdate();
+
+		$this->logfile[] = "<strong>Presence:</strong> (type: unsubscribed) - Unsubscribed from $from's presence";
+	}
+
+
+
+	// ======================================================================
+	// Generic handlers
+	// ======================================================================
+
+
+
+	// Generic handler for unsupported requests
+	function Handler_NOT_IMPLEMENTED($packet)
+	{
+		$packet_type	= $this->_get_packet_type($packet);
+		$from			= call_user_func(array(&$this, "GetInfoFrom" . ucfirst($packet_type) . "From"), $packet);
+		$id				= call_user_func(array(&$this, "GetInfoFrom" . ucfirst($packet_type) . "Id"), $packet);
+
+		$this->SendError($from, $id, 501);
+		$this->logfile[] = "<strong>Unrecognized &lt;$packet_type/&gt;</strong> from $from";
+	}
+
+
+
+	// ======================================================================
+	// Third party code
+	// m at d pr0ps to the coders ;)
+	// ======================================================================
+
+
+
+	// xmlize()
+	// (c) Hans Anderson / http://www.hansanderson.com/php/xml/
+	
+	function xmlize($data) {
+		$vals = $index = $array = array();
+		$parser = xml_parser_create();
+		xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
+		xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
+		xml_parse_into_struct($parser, $data, $vals, $index);
+		xml_parser_free($parser);
+	
+		$i = 0; 
+	
+		$tagname = $vals[$i]['tag'];
+		$array[$tagname]["@"] = $vals[$i]["attributes"];
+		$array[$tagname]["#"] = $this->_xml_depth($vals, $i);
+	
+		return $array;
+	}
+	
+	
+	
+	// _xml_depth()
+	// (c) Hans Anderson / http://www.hansanderson.com/php/xml/
+	
+	function _xml_depth($vals, &$i) { 
+		$children = array(); 
+
+		if ($vals[$i]['value']) {
+			array_push($children, trim($vals[$i]['value']));
+		}
+	
+		while (++$i < count($vals)) { 
+	
+			switch ($vals[$i]['type']) { 
+	
+				case 'cdata': 
+					array_push($children, trim($vals[$i]['value'])); 
+	 				break; 
+	
+				case 'complete': 
+					$tagname = $vals[$i]['tag'];
+					$size = sizeof($children["$tagname"]);
+					$children[$tagname][$size]["#"] = trim($vals[$i]['value']);
+					if ($vals[$i]["attributes"]) {
+						$children[$tagname][$size]["@"] = $vals[$i]["attributes"];
+					}
+					break; 
+	
+				case 'open': 
+					$tagname = $vals[$i]['tag'];
+					$size = sizeof($children["$tagname"]);
+					if ($vals[$i]["attributes"]) {
+						$children["$tagname"][$size]["@"] = $vals[$i]["attributes"];
+						$children["$tagname"][$size]["#"] = $this->_xml_depth($vals, $i);
+					} else {
+						$children["$tagname"][$size]["#"] = $this->_xml_depth($vals, $i);
+					}
+					break; 
+	
+				case 'close':
+					return $children; 
+					break;
+			} 
+		} 
+	
+		return $children;
+	
+	}
+	
+	
+	
+	// TraverseXMLize()
+	// (c) acebone at f2s.com, a HUGE help!
+	
+	function TraverseXMLize($array, $arrName = "array", $level = 0) {
+		if ($level == 0) {
+			echo "<pre>";
+		}
+	
+		while (list($key, $val) = @each($array)) {
+			if (is_array($val)) {
+				$this->TraverseXMLize($val, $arrName . "[" . $key . "]", $level + 1);
+			} else {
+				echo '$' . $arrName . '[' . $key . '] = "' . $val . "\"\n";
+			}
+		}
+	
+		if ($level == 0) {
+			echo "</pre>";
+		}
+	}
+
+}	
+
+
+
+class MakeXML extends Jabber
+{
+	var $nodes;
+
+
+	function MakeXML()
+	{
+		$nodes = array();
+	}
+
+
+
+	function AddPacketDetails($string, $value = NULL)
+	{
+		if (preg_match("/\(([0-9]*)\)$/i", $string)) {
+			$string .= "/[\"#\"]";
+		}
+
+		$temp = @explode("/", $string);
+		
+		for ($a = 0; $a < count($temp); $a++) {
+			$temp[$a] = preg_replace("/^[@]{1}([a-z0-9_]*)$/i", "[\"@\"][\"\\1\"]", $temp[$a]);
+			$temp[$a] = preg_replace("/^([a-z0-9_]*)\(([0-9]*)\)$/i", "[\"\\1\"][\\2]", $temp[$a]);
+			$temp[$a] = preg_replace("/^([a-z0-9_]*)$/i", "[\"\\1\"]", $temp[$a]);
+		}
+
+		$node = implode("", $temp);
+		
+		// Yeahyeahyeah, I know it's ugly... get over it. ;)
+		echo "\$this->nodes$node = \"" . htmlspecialchars($value) . "\";<br/>";
+		eval("\$this->nodes$node = \"" . htmlspecialchars($value) . "\";");
+	}
+
+
+
+	function BuildPacket($array = NULL)
+	{
+
+		if (!$array) {
+			$array = $this->nodes;
+		}
+
+		if (is_array($array)) {
+
+			array_multisort($array, SORT_ASC, SORT_STRING);
+
+			foreach ($array as $key => $value) {
+	
+				if (is_array($value) && $key == "@") {
+
+					foreach ($value as $subkey => $subvalue) {
+						$subvalue = htmlspecialchars($subvalue);
+						$text .= " $subkey='$subvalue'";
+					}
+
+					$text .= ">\n";
+
+				} elseif ($key == "#") {
+
+					$text .= htmlspecialchars($value);
+
+				} elseif (is_array($value)) {
+
+					for ($a = 0; $a < count($value); $a++) {
+
+						$text .= "<$key";
+
+						if (!$this->_preg_grep_keys("/^@/", $value[$a])) {
+							$text .= ">";
+						}
+
+						$text .= $this->BuildPacket($value[$a]);
+
+						$text .= "</$key>\n";
+					}
+
+				} else {
+
+					$value = htmlspecialchars($value);
+					$text .= "<$key>$value</$key>\n";
+
+				}
+
+			}
+
+			return $text;
+		}
+	}
+
+
+
+	function _preg_grep_keys($pattern, $array)
+	{
+		while (list($key, $val) = each($array)) {
+			if (preg_match($pattern, $key)) {
+				$newarray[$key] = $val;
+			}
+		}
+		return (is_array($newarray)) ? $newarray : FALSE;
+	}
+}
+
+
+
+class CJP_StandardConnector
+{
+	function OpenSocket($server, $port)
+	{
+		return fsockopen($server, $port);
+	}
+
+
+
+	function CloseSocket($connection)
+	{
+		return fclose($connection);
+	}
+
+
+	function WriteToSocket($connection, $data)
+	{
+		return fwrite($connection, $data);
+	}
+
+
+
+	function ReadFromSocket($connection, $chunksize)
+	{
+		return fread($connection, $chunksize);
+	}
+}
+
+
+
+?>

Added: trunk/gforge_base/gforge/common/include/MailParser.class
===================================================================
--- trunk/gforge_base/gforge/common/include/MailParser.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/MailParser.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,237 @@
+<?php
+/*
+ * Copyright 2004 GForge, LLC
+ *
+ * @version   $Id: MailParser.class 5240 2006-02-01 12:47:36Z danper $
+ * @author Tim Perdue tim at gforge.org
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/include/Error.class');
+
+class MailParser extends Error {
+
+	var $max_file_size=2000000;
+	var $headers;
+	var $body;
+
+	function MailParser($input_file) {
+		$this->Error();
+		$size = filesize($input_file);
+		if ($size > $this->max_file_size) {
+			$this->setError("Error - file too large");
+			return false;
+		}
+		$fo = fopen($input_file, 'r');
+		$input_data = fread($fo, $size);
+		fclose($fo);
+
+		$lines=explode("\n",$input_data);
+		$linecount=count($lines);
+		unset($input_data);
+
+//system("echo \"mp: headers".implode("***\n",$lines)."\n\" >> /tmp/forum.log");
+		//
+		//	Read the message line-by-line
+		//
+		for ($i=0; $i<($linecount-1); $i++) {
+//system("echo \"mp: line $i of $linecount length: ".strlen($lines[$i])." ".$lines[$i]."\n\" >> /tmp/forum.log");
+			//
+			//	Still reading headers
+			//
+			if (!$got_headers) {
+				//
+				//	If we hit a blank line, end of headers
+				//
+				if (strlen($lines[$i]) < 2) {
+					$got_headers=true;
+				} else {
+					//
+					//	See if line starts with tab, if so ignore it for now
+					//
+					if (!ereg('^[A-z]',$lines[$i])) {
+						$header[$lastheader] = $header[$lastheader]."\n".$lines[$i];
+					} else {
+						$pos = (strpos($lines[$i],':'));
+						$header[substr($lines[$i],0,$pos)] = trim(substr($lines[$i],$pos+2,(strlen($lines[$i])-$pos-2)));
+						$lastheader=substr($lines[$i],0,$pos);
+					}
+				}
+			} else {
+				$body .= $lines[$i]."\r\n";
+			}
+
+
+		}
+		$this->body =& $body;
+		$this->headers =& $header;
+
+		if ($header['Content-Type']) {
+			$hdr = strtolower($header['Content-Type']);
+			if (strpos($hdr,'text/plain') !== false) {
+			
+			} else {
+				$this->setError('Error - only text/plain supported at this time');
+				return false;
+			}
+		}
+//echo "\n\n**".$header['Content-Type']."**\n\n";
+
+
+
+		unset ($lines);
+//system("echo \"mp: headers".implode("***\n",$header)."\n\" >> /tmp/forum.log");
+//system("echo \"mp: body".$body."\n\" >> /tmp/forum.log");
+		return true;
+	}
+
+	function &getBody() {
+		return $this->body;
+	}
+
+	function &getHeader($header) {
+		return $this->headers[$header];
+	}
+
+	function getSubject() {
+		return $this->getHeader('Subject');
+	}
+
+//tperdue at collab.adpcorp.com (Tim Perdue)
+	function getFromEmail() {
+		$mail = $this->getHeader('From');
+		if (strpos($mail,'(') !== false) {
+			$email = substr($mail,0,strpos($mail,' '));
+		} elseif (strpos($mail,'<') !== false) {
+			$begin=(strpos($mail,'<')+1);
+			$end = strpos($mail,'>');
+			$email = substr($mail,$begin,($end-$begin));
+		} else {
+			$email = $mail;
+		}
+		$email = str_replace('"','',$email);
+
+//echo "***$mail*$begin*$end**".$email."*****";
+//system("echo \"mp: email".$email."\n\" >> /tmp/forum.log");
+		return trim($email);
+	}
+
+	/*------------------------------------------------------------------------
+	 *  MIME decoding functions
+	 *-----------------------------------------------------------------------*/
+	/*
+	 * Subject and From decode implementation of RFC 2047
+	 *
+	 * @param String one or more encoded strings
+	 * @return String strcat of all texts. Ignore all charsets
+	 */
+	function mime_header_decode_string($string) {
+
+		$decoded_arr = $this->mime_header_decode($string);
+
+		$return_string = $decoded_arr[0]['text'];
+
+		/* Need a space? */
+		for ($i=1; $i<count($decoded_arr); $i++) {
+			$return_string.=$decoded_arr[$i]['text'];
+		}
+
+		DBG("mime_header: $string -> $return_string \n");
+
+		return $return_string;
+	}
+
+	/**
+	 * Mime header decoding
+	 *
+	 * @param String to decode
+	 * @return Decoded String Array. return['charset'] and retutn['text']
+	 *
+	 *# FIXME: Should we use imap_mime_headres_decode? It's too havey to install
+	 *  See http://us2.php.net/manual/en/function.imap-mime-header-decode.php
+	 *
+	 */
+	function mime_header_decode($string) {
+		/* We expecting series of encoded-word:
+		 * encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
+		 * See more detail in RFC 2407
+		 */
+		$count=0;
+		$strlen = strlen($string);
+
+		for ($i=0; $i < $strlen; $i++) {
+			/* Start seperation */
+			if (!strcmp($string{$i} . $string{$i+1}, "=?")) {
+				$count++;
+			}
+
+			/* End seperation */
+			if( !strcmp($string{$i} . $string{$i+1}, "?=")) {
+				$encoded_word_arr[$count].=$string{$i};
+				$encoded_word_arr[$count].=$string{++$i};
+				$count++; /* Null array should be OK */
+				continue;
+			}
+
+			$encoded_word_arr[$count].=$string{$i};
+		}
+
+		for ($i=0; $i<count($encoded_word_arr); $i++) {
+			$return_arr[$i] = $this->mime_header_one_word_decode($encoded_word_arr[$i]);
+		}
+
+		return $return_arr;
+	}
+
+	/**
+	 * one word decode implementation of RFC 2047
+	 */
+	function mime_header_one_word_decode($string) {
+		/* Default charset */
+		$charset = "ASCII";
+
+		/* We ecpecting : encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
+		 * See more detail in RFC 2407
+		 */
+
+		/* No encoded-word, return default */
+		if (strncmp($string, "=?", 2)) {
+			return array("charset"=>$charset, "text" => $string);
+		}
+
+		/*
+		 * Expecting [0]='=', [1]=charset, [2]=B|Q, [3]=encoded-text
+		 */
+		$string_arr = explode('?', $string);
+
+		if (!strcasecmp($string_arr[2], "B") && $string_arr[3]) {
+			$string = base64_decode($string_arr[3]);
+			$charset = $string_arr[1];
+		} else if (!strcasecmp($string_arr[2], "Q") && $string_arr[3]) {
+			$string = quoted_printable_decode($string_arr[3]);
+			$charset = $string_arr[1];
+		}
+
+		/* Return what we have */
+		$ret_arr = array("charset"=>$charset, "text" => $string);
+		return $ret_arr;
+	}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/include/Permission.class
===================================================================
--- trunk/gforge_base/gforge/common/include/Permission.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/Permission.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,336 @@
+<?php
+/**
+ * A base permissions class.
+ *
+ * Portions Copyright 1999-2001 (c) VA Linux Systems
+ * The rest Copyright 2002-2004 (c) GForge Team
+ * http://gforge.org/
+ *
+ * @version   $Id: Permission.class 4994 2005-11-25 15:53:25Z danper $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/include/Error.class');
+
+$PERMISSION_OBJ=array();
+
+/**
+ * permission_get_object() - Get permission objects
+ *
+ * permission_get_object is useful so you can pool Permission objects/save database queries
+ * You should always use this instead of instantiating the object directly 
+ *
+ * @param		object	The Group in question
+ * @param		object	The User needing Permission
+ * @return a Permission or false on failure
+ *
+ */
+function &permission_get_object(&$_Group, &$_User) {
+	//create a common set of Permission objects
+	//saves a little wear on the database
+	
+	global $PERMISSION_OBJ;
+
+	if (is_object($_Group)) {
+		$group_id = $_Group->getID();
+	} else {
+		$group_id = 0;
+	}
+
+	if (is_object($_User)) {
+		$user_id = $_User->getID();
+	} else {
+		//invalid object, probably from user not being logged in
+		$user_id = 0;
+	}
+
+	if (!isset($PERMISSION_OBJ["_".$group_id."_".$user_id])) {
+		$PERMISSION_OBJ["_".$group_id."_".$user_id]= new Permission($_Group, $_User);
+	}
+	return $PERMISSION_OBJ["_".$group_id."_".$user_id];
+}
+
+class Permission extends Error {
+	/**
+	 * Associative array of data from db.
+	 *
+	 * @var array $data_array.
+	 */
+	var $data_array;
+
+	/**
+	 * The Group object.
+	 *
+	 * @var object $Group.
+	 */
+	var $Group;
+
+	/**
+	 * The User object.
+	 *
+	 * @var object $User.
+	 */
+	var $User;
+
+	/**
+	 * Whether the user is an admin/super user of this project.
+	 *
+	 * @var bool $is_admin.
+	 */
+	var $is_admin=false;
+
+	/**
+	 * Whether the user is an admin/super user of the entire site.
+	 *
+	 * @var bool $is_site_admin.
+	 */
+	var $is_site_admin;
+
+	/**
+	 *	Constructor for this object.
+	 *
+	 *	@param	object	Group Object required.
+	 *	@param	object	User Object required.
+	 *	
+	 */
+	function Permission (&$_Group, &$_User) {
+		if (!$_Group || !is_object($_Group)) {
+			$this->setError('No Valid Group Object');
+			return false;
+		}
+		if ($_Group->isError()) {
+			$this->setError('Permission: '.$_Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $_Group;
+
+		if (!$_User || !is_object($_User)) {
+			$this->setError('No Valid User Object');
+			return false;
+		}   
+		if ($_User->isError()) {
+			$this->setError('Permission: '.$_User->getErrorMessage());
+			return false;
+		}   
+		$this->User =& $_User;
+
+		if (!$this->fetchData()) {
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 *  fetchData - fetch the data for this Permission from the database.
+	 *
+	 *  @return	boolean success.
+	 *	@access private.
+	 */
+	function fetchData() {
+		$res=db_query("SELECT * FROM user_group 
+			WHERE user_id='". $this->User->getID() ."' 
+			AND group_id='". $this->Group->getID() ."'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('Permission: User Not Found');
+
+			if ($this->setUpSuperUser()) {
+				return true;
+			}
+		} else {
+			$this->data_array =& db_fetch_array($res);
+			if (trim($this->data_array['admin_flags']) == 'A') {
+				$this->is_admin=true;
+			} else {
+				$this->setUpSuperUser();
+			}
+			db_free_result($res);
+			return true;
+		}
+	}
+
+	/**
+	 *	setUpSuperUser - check to see if this User is a site super-user.
+	 *
+	 *	@return	boolean	is_super_user.
+	 *	@access private
+	 */
+	function setUpSuperUser() {
+		//
+		//  see if they are a site super-user
+		//  if not a member of this group
+		//
+		if ($this->isSuperUser()) {
+			$this->clearError();
+			$this->is_admin = true;
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 *	getUser - get the User object this Permission is associated with.
+	 *
+	 *	@return	object	The User object.
+	 */
+	function &getUser() {
+		return $this->User;
+	}
+
+	/**
+	 *	getGroup - get the Group object this Permission is associated with.
+	 *
+	 *	@return the Group object.
+	 */
+	function &getGroup() {
+		return $this->Group;
+	}
+
+	/**
+	 *  isSuperUser - whether the current user has site admin privilege.
+	 *
+	 *  @return	boolean	is_super_user.
+	 */
+	function isSuperUser() {
+		if (isset($this->is_site_admin)) {
+			return $this->is_site_admin;
+		}
+
+		$res = db_query("SELECT count(*) FROM user_group
+			WHERE user_id='". $this->User->getID() ."'
+			AND group_id='1'
+			AND admin_flags='A'");
+		$row_count = db_fetch_array($res);
+		$this->is_site_admin = $res && $row_count['count'] > 0;
+		db_free_result($res);
+
+		return $this->is_site_admin;
+	}
+
+	/**
+	 *  isForumAdmin - whether the current user has form admin perms.
+	 *
+	 *  @return	boolean	is_forum_admin.
+	 */
+	function isForumAdmin() {
+		return $this->isMember('forum_flags',2);
+	}
+
+	/**
+	 *  isDocEditor - whether the current user has form doc editor perms.
+	 *
+	 *  @return	boolean	is_doc_editor.
+	 */
+	function isDocEditor() {
+		return $this->isMember('doc_flags',1);
+	}
+
+	/**
+	 *  isReleaseTechnician - whether the current user has FRS admin perms.
+	 *
+	 *  @return	boolean	is_release_technician.
+	 */
+	function isReleaseTechnician() {
+		return $this->isMember('release_flags',1);
+	}
+
+	/**
+	 *  isArtifactAdmin - whether the current user has artifact admin perms.
+	 *
+	 *  @return	boolean	is_artifact_admin.
+	 */
+	function isArtifactAdmin() {
+		return $this->isMember('artifact_flags',2);
+	}
+
+	/**
+	 *  isPMAdmin - whether the current user has Task Manager admin perms.
+	 *
+	 *  @return	boolean	is_projman_admin.
+	 */
+	function isPMAdmin() {
+		return $this->isMember('project_flags',2);
+	}
+
+	/**
+	 *  isMember - Simple test to see if the current user is a member of this project.
+	 *
+	 *  Can optionally pass in vars to test other permissions.
+	 *
+	 *  @param string	The field to check.
+	 *  @param int		The value that $field should have.
+	 *  @return	boolean	is_member.
+	 */
+	function isMember($field='user_id',$value='-1') {
+		if ($this->isAdmin()) {
+			//admins are tested first so that super-users can return true
+			//and admins of a project should always have full privileges 
+			//on their project
+			return true;
+		} else {
+			$arr =& $this->getPermData();
+			if ($arr[$field] >= $value) {
+				return true; 
+			} else {
+				return false;
+			}
+		}
+	}
+
+	/**
+	 *  isAdmin - User is an admin of the project or admin of the entire site.
+	 *
+	 *  @return	boolean	is_admin.
+	 */
+	function isAdmin() {
+		return $this->is_admin;
+	}
+
+	/**
+	 *	getPermData - returns the assocative array from the db.
+	 *
+	 *	@return array The array of data.
+	 *	@access private.
+	 */
+	function &getPermData() {
+		return $this->data_array;
+	}
+
+	/**
+	 *	isCVSReader - checks the cvs_flags field in user_group table.
+	 *
+	 *	@return	boolean	cvs_flags
+	 */
+	function isCVSReader() {
+		return $this->isMember('cvs_flags',0);
+	}
+	
+	
+	/**
+	 *      isCVSWriter - checks if the user has CVS write access.
+	 *
+	 *      @return boolean cvs_flags
+	 */
+	function isCVSWriter() {
+		return $this->isMember('cvs_flags',1);
+	}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/include/Plugin.class
===================================================================
--- trunk/gforge_base/gforge/common/include/Plugin.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/Plugin.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,94 @@
+<?php
+/**
+ *	Plugin object
+ *
+ *	Provides an base class for a plugin
+ *
+ * This file is copyright (c) Roland Mas <lolando at debian.org>, 2002
+ *
+ * $Id: Plugin.class 3113 2004-07-21 21:58:12Z cbayle $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+class Plugin extends Error {
+	var $name ;
+	var $hooks ;
+
+	/**
+	 * Plugin() - constructor
+	 *
+	 */
+	function Plugin () {
+		$this->Error() ;
+		$this->name = false ;
+		$this->hooks = array () ;
+	}
+
+	/**
+	 * GetHooks() - get list of hooks to subscribe to
+	 *
+	 * @return List of strings
+	 */
+	function GetHooks () {
+		return $this->hooks ;
+	}
+
+	/**
+	 * GetName() - get plugin name
+	 *
+	 * @return the plugin name
+	 */
+	function GetName () {
+		return $this->name ;
+	}
+
+	/**
+	 * CallHook() - call a particular hook
+	 *
+	 * @param hookname - the "handle" of the hook
+	 * @param params - array of parameters to pass the hook
+	 */
+	function CallHook ($hookname, $params) {
+		return true ;
+	}
+
+	/**
+	 * GetLanguagePath() - get the path where we can find i18n for the plugin
+	 *
+	 * @return string path
+	 */
+	function GetLanguagePath() {
+		return $GLOBALS['sys_plugins_path'].'/'.$this->name.'/include/languages/';
+	}
+
+	/**
+	 * GetSpecificLanguagePath() - get the path where we can find installation specific language files
+	 *
+	 * @return string path
+	 */
+	function GetSpecificLanguagePath() {
+		return '/etc/gforge/plugins/'.$this->name.'/languages/';
+	}
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/include/PluginManager.class
===================================================================
--- trunk/gforge_base/gforge/common/include/PluginManager.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/PluginManager.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,254 @@
+<?php
+/**
+ *	PluginManager object
+ *
+ *	Provides an abstract way to handle plugins
+ *
+ * This file is copyright (c) Roland Mas <lolando at debian.org>, 2002
+ *
+ * @version   $Id: PluginManager.class 5541 2006-06-06 21:27:52Z lo-lan-do $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+class PluginManager extends Error {
+	var $plugins_objects ;
+	var $plugins_to_hooks ;
+	var $hooks_to_plugins ;
+
+	/**
+	 * PluginManager() - constructor
+	 *
+	 */
+	function PluginManager () {
+		$this->Error() ;
+		$this->plugins_objects = array () ;
+		$this->plugins_to_hooks = array () ;
+		$this->hooks_to_plugins = array () ;
+	}
+
+	/**
+	 * GetPlugins() - get a list of installed plugins
+	 *
+	 * @return hash of plugin id => plugin names
+	 */
+	function GetPlugins () {
+		if (!isset($this->plugins_data)) {
+			$this->plugins_data = array () ;
+			$sql = "SELECT plugin_id, plugin_name FROM plugins" ;
+			$res = db_query($sql);
+			$rows = db_numrows($res);
+			for ($i=0; $i<$rows; $i++) {
+				$plugin_id = db_result($res,$i,'plugin_id');
+				$this->plugins_data[$plugin_id] = db_result($res,$i,'plugin_name');
+			}
+		}
+		return $this->plugins_data ;
+	}
+
+	/**
+	 * GetPluginObject() - get a particular plugin object
+	 *
+         * @param pluginname - name of plugin
+	 * @return a plugin object
+	 */
+	function GetPluginObject ($pluginname) {
+		return $this->plugins_objects [$pluginname] ;
+	}
+
+	/**
+	 * PluginIsInstalled() - is a plugin installed?
+	 *
+	 * @param pluginname - name of plugin
+	 * @return boolean, true if installed
+	 */
+	function PluginIsInstalled ($pluginname) {
+		$plugins_data = $this->getPlugins() ;
+		foreach ($plugins_data as $p_id => $p_name) {
+			if ($p_name == $pluginname) {
+				return true ;
+			}
+		}
+		return false ;
+	}
+
+	/**
+	 * LoadPlugins() - load available plugins
+	 *
+	 */
+	function LoadPlugins () {
+		$plugins_data = $this->GetPlugins() ;
+		$include_path = $GLOBALS['sys_plugins_path'] ;
+		foreach ($plugins_data as $p_id => $p_name) {
+			$filename = $include_path . $p_name . "/include/".$p_name."-init.php" ;
+			if (file_exists ($filename)) {
+				require_once ($filename) ;
+			} else {
+				// we can't find the plugin so we remove it from the array
+				unset($this->plugins_data[$p_id]);
+			}
+		}
+		return true ;
+	}
+
+	/**
+	 * SetupHooks() - setup all hooks for installed plugins
+	 *
+	 */
+	function SetupHooks () {
+		foreach ($this->plugins_to_hooks as $p_name => $hook_list) {
+			foreach ($hook_list as $hook_name) {
+				if (!isset ($this->hooks_to_plugins[$hook_name])) {
+					$this->hooks_to_plugins[$hook_name] = array () ;
+				}
+				$this->hooks_to_plugins[$hook_name][] = $p_name ;
+			}
+		}
+		return true ;
+	}
+
+	/**
+	 * RegisterPlugin() - register a plugin
+	 *
+	 * @param pluginobject - an object of a subclass of the Plugin class
+	 */
+	function RegisterPlugin (&$pluginobject) {
+		if (!$pluginobject->GetName ()) {
+			exit_error ("Some plugin did not provide a name.  I'd gladly tell you which one, but obviously I can't.  Sorry.") ;
+		}
+		$p_name = $pluginobject->GetName () ;
+		$this->plugins_objects [$p_name] =& $pluginobject ;
+		$this->plugins_to_hooks [$p_name] = $pluginobject->GetHooks () ;
+		return true ;
+	}
+
+	/**
+	 * RunHooks() - call hooks from a particular point
+	 *
+	 * @param hookname - name of the hook
+	 * @param params - array of extra parameters
+	 */
+	function RunHooks ($hookname, & $params) {
+		if (isset($this->hooks_to_plugins[$hookname])) {
+			$p_list = $this->hooks_to_plugins[$hookname];
+			foreach ($p_list as $p_name) {
+				$p_obj = $this->plugins_objects[$p_name] ;
+				$p_obj->CallHook ($hookname, $params) ;
+			}
+		}
+		return true ;
+	}
+
+	/**
+	 * CountHookListeners() - number of listeners on a particular hook
+	 *
+	 * @param hookname - name of the hook
+	 */
+	function CountHookListeners ($hookname) {
+		if (isset($this->hooks_to_plugins[$hookname])) {
+			$p_list = $this->hooks_to_plugins[$hookname];
+			return count ($p_list) ;
+		} else {
+			return 0 ;
+		}
+		
+	}
+}
+
+/**
+ * plugin_manager_get_object() - get the PluginManager object
+ *
+ * @return the PluginManager object
+ */
+function &plugin_manager_get_object() {
+	global $PLUGINMANAGER_OBJ;
+	if (!isset($PLUGINMANAGER_OBJ) || !$PLUGINMANAGER_OBJ) {
+		$PLUGINMANAGER_OBJ = new PluginManager ;
+	}
+	return $PLUGINMANAGER_OBJ ;
+}
+
+/**
+ * plugin_get_object() - get a particular Plugin object
+ *
+ * @param pluginname - a plugin name
+ * @return the Plugin object
+ */
+function &plugin_get_object ($pluginname) {
+	global $PLUGINMANAGER_OBJ;
+	return $PLUGINMANAGER_OBJ->Getpluginobject ($pluginname) ;
+}
+
+/**
+ * register_plugin () - register a plugin
+ *
+ * @param pluginobject - an object of a subclass of the Plugin class
+ */
+function register_plugin (&$pluginobject) {
+	$pm =& plugin_manager_get_object () ;
+	return $pm->RegisterPlugin ($pluginobject) ;
+}
+
+/**
+ * plugin_hook () - run a set of hooks
+ *
+ * @param hookname - name of the hook
+ * @param params - parameters for the hook
+ */
+function plugin_hook ($hookname, $params = false) {
+	$pm =& plugin_manager_get_object () ;
+	return $pm->RunHooks ($hookname, $params) ;
+}
+
+/**
+ * plugin_hook_by_reference () - run a set of hooks with params passed by reference
+ *
+ * @param hookname - name of the hook
+ * @param params - parameters for the hook
+ */
+function plugin_hook_by_reference ($hookname, & $params) {
+	$pm =& plugin_manager_get_object () ;
+	return $pm->RunHooks ($hookname, $params) ;
+}
+
+/**
+ * plugin_hook_listeners () - count the number of listeners on a hook
+ *
+ * @param hookname - name of the hook
+ */
+function plugin_hook_listeners ($hookname, $params=false) {
+	$pm =& plugin_manager_get_object () ;
+	return $pm->CountHookListeners ($hookname) ;
+}
+
+/**
+ * setup_plugin_manager () - initialise the plugin infrastructure
+ *
+ */
+function setup_plugin_manager () {
+	$pm =& plugin_manager_get_object() ;
+	$pm->LoadPlugins () ;
+	$pm->SetupHooks () ;
+	return true ;
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/include/Role.class
===================================================================
--- trunk/gforge_base/gforge/common/include/Role.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/Role.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,722 @@
+<?php
+/**
+ * Role Class
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: Role.class 4367 2005-05-17 18:54:58Z cbayle $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2004-03-16
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+class Role extends Error {
+
+	var $data_array;
+	var $setting_array;
+	var $role_vals;
+	var $Group;
+	var $role_values=array(
+	'projectadmin'=>array('0','A'),
+	'frs'=>array('0','1'),
+	'scm'=>array('-1','0','1'),
+	'docman'=>array('0','1'),
+	'forumadmin'=>array('0','2'),
+	'forum'=>array('-1','0','1','2'),
+	'trackeradmin'=>array('0','2'),
+	'tracker'=>array('-1','0','1','2','3'),
+	'pmadmin'=>array('0','2'),
+	'pm'=>array('-1','0','1','2','3'));
+
+	var $defaults=array(
+		'Admin'=>array( 'projectadmin'=>'A', 'frs'=>'1', 'scm'=>'1', 'docman'=>'1', 'forumadmin'=>'2', 'forum'=>'2', 'trackeradmin'=>'2', 'tracker'=>'2', 'pmadmin'=>'2', 'pm'=>'2' ),
+		'Senior Developer'=>array( 'projectadmin'=>'0', 'frs'=>'1', 'scm'=>'1', 'docman'=>'1', 'forumadmin'=>'2', 'forum'=>'2', 'trackeradmin'=>'2', 'tracker'=>'2', 'pmadmin'=>'2', 'pm'=>'2' ),
+		'Junior Developer'=>array( 'projectadmin'=>'0', 'frs'=>'0', 'scm'=>'1', 'docman'=>'0', 'forumadmin'=>'0', 'forum'=>'1', 'trackeradmin'=>'0', 'tracker'=>'1', 'pmadmin'=>'0', 'pm'=>'1' ),
+		'Doc Writer'=>array( 'projectadmin'=>'0', 'frs'=>'0', 'scm'=>'0', 'docman'=>'1', 'forumadmin'=>'0', 'forum'=>'1', 'trackeradmin'=>'0', 'tracker'=>'0', 'pmadmin'=>'0', 'pm'=>'0' ),
+		'Support Tech'=>array( 'projectadmin'=>'0', 'frs'=>'0', 'scm'=>'0', 'docman'=>'1', 'forumadmin'=>'0', 'forum'=>'1', 'trackeradmin'=>'0', 'tracker'=>'2', 'pmadmin'=>'0', 'pm'=>'0' )
+	);
+	
+	/**
+	 *  Role($group,$id) - CONSTRUCTOR.
+	 *
+	 *  @param  object	 The Group object.
+	 *  @param  int	 The role_id.
+	 */
+	function Role ($Group,$role_id=false) {
+		$this->Error();
+		if (!$Group || !is_object($Group) || $Group->isError()) {
+			$this->setError('Role::'.$Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $Group;
+		if (!$role_id) {
+			//setting up an empty object
+			//probably going to call create()
+			return true;
+		}
+		return $this->fetchData($role_id);
+	}
+
+	/**
+	 *	getID - get the ID of this role.
+	 *
+	 *	@return	integer	The ID Number.
+	 */
+	function getID() {
+		return $this->data_array['role_id'];
+	}
+
+	/**
+	 *	getName - get the name of this role.
+	 *
+	 *	@return	string	The name of this role.
+	 */
+	function getName() {
+		return $this->data_array['role_name'];
+	}
+
+	/**
+	 *	create - create a new role in the database.
+	 *
+	 *	@param	string	The name of the role.
+	 *	@param	array	A multi-dimensional array of data in this format: $data['section_name']['ref_id']=$val
+	 *	@return integer	The id on success or false on failure.
+	 */
+	function create($role_name,$data) {
+		$perm =& $this->Group->getPermission( session_get_user() );
+		if (!$perm || !is_object($perm) || $perm->isError() || !$perm->isAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		db_begin();
+		$sql="INSERT INTO role (group_id,role_name) 
+			VALUES ('".$this->Group->getID()."','".htmlspecialchars($role_name)."')";
+//echo "\n<br>$sql";
+		$res=db_query($sql);
+		if (!$res) {
+			$this->setError('create::'.db_error());
+			db_rollback();
+			return false;
+		}
+		$role_id=db_insertid($res,'role','role_id');
+		if (!$role_id) {
+			$this->setError('create::db_insertid::'.db_error());
+			db_rollback();
+			return false;
+		}
+
+		$arr1 = array_keys($data);
+		for ($i=0; $i<count($arr1); $i++) {	
+		//	array_values($Report->adjust_days)
+			$arr2 = array_keys($data[$arr1[$i]]);
+			for ($j=0; $j<count($arr2); $j++) {
+				$usection_name=$arr1[$i];
+				$uref_id=$arr2[$j];
+				$uvalue=$data[$arr1[$i]][$arr2[$j]];
+				if (!$uref_id) {
+					$uref_id=0;
+				}
+				if (!$uvalue) {
+					$uvalue=0;
+				}
+				$sql="INSERT INTO role_setting (role_id,section_name,ref_id,value) 
+					values ('$role_id','$usection_name', '$uref_id','$uvalue')";
+//echo "\n<br>$sql";
+				$res=db_query($sql);
+				if (!$res) {
+					$this->setError('create::insertsetting::'.db_error());
+					db_rollback();
+					return false;
+				}
+			}
+		}
+		db_commit();
+		return $role_id;
+	}
+
+	function createDefault($name) {
+//echo '<html><body><pre>';
+//echo $name;
+//print_r($this->defaults);
+		$arr =& $this->defaults[$name];
+		$keys = array_keys($arr);
+		$data = array();
+
+//print_r($keys);
+//print_r($arr);
+//db_rollback();
+//exit;
+		for ($i=0; $i<count($keys); $i++) {
+
+			if ($keys[$i] == 'forum') {
+				$res=db_query("SELECT group_forum_id 
+					FROM forum_group_list 
+					WHERE group_id='".$this->Group->getID()."'");
+				if (!$res) {
+					$this->setError('Error: Forum'.db_error());
+					return false;
+				}
+				for ($j=0; $j<db_numrows($res); $j++) {
+					$data[$keys[$i]][db_result($res,$j,'group_forum_id')]= $arr[$keys[$i]];
+				}
+			} elseif ($keys[$i] == 'pm') {
+				$res=db_query("SELECT group_project_id 
+					FROM project_group_list 
+					WHERE group_id='".$this->Group->getID()."'");
+				if (!$res) {
+					$this->setError('Error: TaskMgr'.db_error());
+					return false;
+				}
+				for ($j=0; $j<db_numrows($res); $j++) {
+					$data[$keys[$i]][db_result($res,$j,'group_project_id')]= $arr[$keys[$i]];
+				}
+			} elseif ($keys[$i] == 'tracker') {
+				$res=db_query("SELECT group_artifact_id 
+					FROM artifact_group_list 
+					WHERE group_id='".$this->Group->getID()."'");
+				if (!$res) {
+					$this->setError('Error: Tracker'.db_error());
+					return false;
+				}
+				for ($j=0; $j<db_numrows($res); $j++) {
+					$data[$keys[$i]][db_result($res,$j,'group_artifact_id')]= $arr[$keys[$i]];
+				}
+			} else {
+				$data[$keys[$i]][0]= $arr[$keys[$i]];
+			}
+		}
+//print_r($data);
+//db_rollback();
+//exit;
+		return $this->create($name,$data);
+	}
+
+	/**
+	 *  fetchData - May need to refresh database fields.
+	 *
+	 *  If an update occurred and you need to access the updated info.
+	 *
+	 *  @return boolean success;
+	 */
+	function fetchData($role_id) {
+		unset($this->data_array);
+		unset($this->setting_array);
+		$res=db_query("SELECT * FROM role WHERE role_id='$role_id'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('Role::fetchData()::'.db_error());
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		$res=db_query("SELECT * FROM role_setting WHERE role_id='$role_id'");
+		if (!$res) {
+			$this->setError('Role::fetchData()::'.db_error());
+			return false;
+		}
+		$this->setting_array=array();
+		while ($arr =& db_fetch_array($res)) {
+			$this->setting_array[$arr['section_name']][$arr['ref_id']] = $arr['value'];
+		}
+		return true;
+	}
+
+	/**
+	 *  &getRoleVals - get all the values and language text strings for this section.
+	 *
+	 *  @return array	Assoc array of values for this section.
+	 */
+	function &getRoleVals($section) {
+		global $Language,$role_vals;
+
+		//
+		//	Optimization - save array so it is only built once per page view
+		//
+		if (!isset($role_vals[$section])) {
+
+			for ($i=0; $i<count($this->role_values[$section]); $i++) {
+				//
+				//	Build an associative array of these key values + localized description
+				//
+				$role_vals[$section][$this->role_values[$section][$i]]=$Language->getText('rbac_vals',"$section".$this->role_values[$section][$i]);
+			}
+		}
+		return $role_vals[$section];
+	}
+
+	/**
+	 *	getVal - get a value out of the array of settings for this role.
+	 *
+	 *	@param	string	The name of the role.
+	 *	@param	integer	The ref_id (ex: group_artifact_id, group_forum_id) for this item.
+	 *	@return integer	The value of this item.
+	 */
+	function getVal($section,$ref_id) {
+		global $role_default_array;
+		if (!$ref_id) {
+			$ref_id=0;
+		}
+		return $this->setting_array[$section][$ref_id];
+	}
+
+	/**
+	 *	update - update a new in the database.
+	 *
+	 *	@param	string	The name of the role.
+	 *	@param	array	A multi-dimensional array of data in this format: $data['section_name']['ref_id']=$val
+	 *	@return	boolean	True on success or false on failure.
+	 */
+	function update($role_name,$data) {
+		global $SYS;
+		//
+		//	Cannot update role_id=1
+		//
+		if ($this->getID() == 1) {
+			$this->setError('Cannot Update Default Role');
+			return false;
+		}
+		$perm =& $this->Group->getPermission( session_get_user() );
+		if (!$perm || !is_object($perm) || $perm->isError() || !$perm->isAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		db_begin();
+
+		if ($this->getName() != stripslashes($role_name)) {
+			$sql="UPDATE role
+				SET role_name='".htmlspecialchars($role_name)."'
+				WHERE group_id='".$this->Group->getID()."'
+				AND role_id='".$this->getID()."'";
+//echo "\n<br>$sql";
+			$res=db_query($sql);
+			if (!$res || db_affected_rows($res) < 1) {
+				$this->setError('update::name::'.db_error());
+				db_rollback();
+				return false;
+			}
+		}
+////$data['section_name']['ref_id']=$val
+		$arr1 = array_keys($data);
+		for ($i=0; $i<count($arr1); $i++) {	
+		//	array_values($Report->adjust_days)
+			$arr2 = array_keys($data[$arr1[$i]]);
+			for ($j=0; $j<count($arr2); $j++) {
+				$usection_name=$arr1[$i];
+				$uref_id=$arr2[$j];
+				$uvalue=$data[$usection_name][$uref_id];
+				if (!$uref_id) {
+					$uref_id=0;
+				}
+				if (!$uvalue) {
+					$uvalue=0;
+				}
+				//
+				//	See if this setting changed. If so, then update it
+				//
+//				if ($this->getVal($usection_name,$uref_id) != $uvalue) {
+					$sql="UPDATE role_setting 
+						SET value='$uvalue' 
+						WHERE role_id='".$this->getID()."' 
+						AND section_name='$usection_name'
+						AND ref_id='$uref_id'";
+//echo "\n<br>$sql";
+					$res=db_query($sql);
+					if (!$res  || db_affected_rows($res) < 1) {
+						$sql="INSERT INTO role_setting (role_id,section_name,ref_id,value) 
+						values ('".$this->getID()."','$usection_name', '$uref_id','$uvalue')";
+//echo "\n<br>$sql";
+						$res=db_query($sql);
+						if (!$res) {
+							$this->setError('update::rolesettinginsert::'.db_error());
+							db_rollback();
+							return false;
+						}
+					}
+					if ($usection_name == 'frs') {
+						$update_usergroup=true;
+					} elseif ($usection_name == 'scm') {
+						//$update_usergroup=true;
+
+						//iterate all users with this role
+						$res=db_query("SELECT user_id
+							FROM user_group 
+							WHERE role_id='".$this->getID()."'");
+						for ($z=0; $z<db_numrows($res); $z++) {
+
+							//TODO - Shell should be separate flag
+							//  If user acquired admin access to CVS,
+							//  one to be given normal shell on CVS machine,
+							//  else - restricted.
+							//
+							$cvs_flags=$data['scm'][0];
+							$sql="UPDATE user_group
+								SET cvs_flags=".$cvs_flags." 
+								WHERE user_id=".db_result($res,$z,'user_id')." AND role_id=".$this->getID();
+							//echo '<h1>'.$data['scm'][0].'::'.$sql.'</h1>';
+							$res2=db_query($sql);
+							if (!$res2) {
+								$this->setError('update::scm::'.db_error());
+								db_rollback();
+								return false;
+							}
+							// I have doubt the following is usefull
+							// This is probably buggy if used
+							if ($cvs_flags>1) {
+								if (!$SYS->sysUserSetAttribute($user_id,"debGforgeCvsShell","/bin/bash")) {
+									$this->setError($SYS->getErrorMessage());
+									db_rollback();
+									return false;
+								}
+							} else {
+								if (!$SYS->sysUserSetAttribute($user_id,"debGforgeCvsShell","/bin/cvssh")) {
+									$this->setError($SYS->getErrorMessage());
+									db_rollback();
+									return false;
+								}
+							}
+
+							//
+							//  If user acquired at least commit access to CVS,
+							//  one to be promoted to CVS group, else, demoted.
+							//
+							if ($uvalue>0) {
+								if (!$SYS->sysGroupAddUser($this->Group->getID(),db_result($res,$z,'user_id'),1)) {
+									$this->setError($SYS->getErrorMessage());
+									db_rollback();
+									return false;
+								}
+							} else {
+								if (!$SYS->sysGroupRemoveUser($this->Group->getID(),db_result($res,$z,'user_id'),1)) {
+									$this->setError($SYS->getErrorMessage());
+									db_rollback();
+									return false;
+								}
+							}
+
+
+						}
+//
+//	If we decide to use a "RBAC Group" to define template roles
+//	The next 3 items will have to be modified to remap IDs for each project
+//
+
+					//
+					//	Forum
+					//
+					} elseif ($usection_name == 'forum') {
+						$sql="UPDATE forum_perm
+							SET perm_level='$uvalue'
+							WHERE
+							group_forum_id='$uref_id'
+							AND user_id IN (SELECT ug.user_id FROM
+							user_group ug, forum_group_list fgl, forum_perm fp
+							WHERE ug.role_id='".$this->getID()."'
+							AND ug.group_id=fgl.group_id AND 
+							fgl.group_forum_id='$uref_id'
+							AND ug.user_id=fp.user_id
+							AND fp.group_forum_id=fgl.group_forum_id)";
+//echo "\n<br>$sql";
+						$res=db_query($sql);
+						if (!$res) {
+							$this->setError('update::forum::'.db_error());
+							db_rollback();
+							return false;
+						}
+					} elseif ($usection_name == 'pm') {
+						$sql="UPDATE project_perm
+							SET perm_level='$uvalue'
+							WHERE
+							group_project_id='$uref_id'
+							AND user_id IN (SELECT ug.user_id FROM
+							user_group ug, project_group_list pgl, project_perm pp
+							WHERE ug.role_id='".$this->getID()."'
+							AND ug.group_id=pgl.group_id AND
+                            pgl.group_project_id='$uref_id'
+							AND ug.user_id=pp.user_id
+							AND pp.group_project_id=pgl.group_project_id)";
+//echo "\n<br>$sql";
+						$res=db_query($sql);
+						if (!$res) {
+							$this->setError('update::pm::'.db_error());
+							db_rollback();
+							return false;
+						}
+					} elseif ($usection_name == 'tracker') {
+						$sql="UPDATE artifact_perm
+							SET perm_level='$uvalue'
+							WHERE
+							group_artifact_id='$uref_id'
+							AND user_id IN (SELECT ug.user_id FROM
+							user_group ug, artifact_group_list agl, artifact_perm ap 
+							WHERE ug.role_id='".$this->getID()."'
+							AND ug.group_id=agl.group_id AND
+                            agl.group_artifact_id='$uref_id'
+							AND ug.user_id=ap.user_id
+							AND agl.group_artifact_id=ap.group_artifact_id)";
+//echo "\n<br>$sql";
+						$res=db_query($sql);
+						if (!$res) {
+							$this->setError('update::tracker::'.db_error());
+							db_rollback();
+							return false;
+						}
+
+					} elseif ($usection_name == 'docman') {
+						$update_usergroup=true;
+					} elseif ($usection_name == 'forumadmin') {
+						$update_usergroup=true;
+					} elseif ($usection_name == 'trackeradmin') {
+						$update_usergroup=true;
+					} elseif ($usection_name == 'projectadmin') {
+						$update_usergroup=true;
+					} elseif ($usection_name == 'pmadmin') {
+						$update_usergroup=true;
+					}
+	//			}
+			}
+		}
+//		if ($update_usergroup) {
+			$sql="UPDATE user_group 
+				SET
+				admin_flags='".$data['projectadmin'][0]."',
+				forum_flags='".$data['forumadmin'][0]."',
+				project_flags='".$data['pmadmin'][0]."',
+				doc_flags='".$data['docman'][0]."',
+				cvs_flags='".$data['scm'][0]."',
+				release_flags='".$data['frs'][0]."',
+				artifact_flags='".$data['trackeradmin'][0]."'
+				WHERE role_id='".$this->getID()."'";
+//echo "\n<br>$sql";
+			$res=db_query($sql);
+			if (!$res) {
+				$this->setError('update::usergroup::'.db_error());
+				db_rollback();
+				return false;
+			}
+
+//		}
+		db_commit();
+		$this->fetchData($this->getID());
+		return true;
+	}
+
+	function setUser($user_id) {
+		global $SYS;
+		$perm =& $this->Group->getPermission( session_get_user() );
+		if (!$perm || !is_object($perm) || $perm->isError() || !$perm->isAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		db_begin();
+
+		//
+		//	See if role is actually changing
+		//
+		$res=db_query("SELECT role_id FROM user_group 
+			WHERE user_id='$user_id' 
+			AND group_id='".$this->Group->getID()."'");
+		$old_roleid=db_result($res,0,0);
+		if ($this->getID() == $old_roleid) {
+			db_commit();
+			return true;
+		}
+		//
+		//	Get the old role so we can compare new values to old
+		//
+		$oldrole= new Role($this->Group,$old_roleid);
+		if (!$oldrole || !is_object($oldrole) || $oldrole->isError()) {
+			$this->setError($oldrole->getErrorMessage());
+			db_rollback();
+			return false;
+		}
+
+		//
+		//	Iterate each setting to see if it's changing
+		//	If not, no sense updating it
+		//
+		$arr1 = array_keys($this->setting_array);
+		for ($i=0; $i<count($arr1); $i++) {	
+		//	array_values($Report->adjust_days)
+			$arr2 = array_keys($this->setting_array[$arr1[$i]]);
+			for ($j=0; $j<count($arr2); $j++) {
+				$usection_name=$arr1[$i];
+				$uref_id=$arr2[$j];
+				$uvalue=$this->setting_array[$usection_name][$uref_id];
+				if (!$uref_id) {
+					$uref_id=0;
+				}
+				if (!$uvalue) {
+					$uvalue=0;
+				}
+				//
+				//	See if this setting changed. If so, then update it
+				//
+	//			if (($this->getVal($usection_name,$uref_id) != $oldrole->getVal($usection_name,$uref_id)) || ($old_roleid == 1)) {
+					if ($usection_name == 'frs') {
+						$update_usergroup=true;
+					} elseif ($usection_name == 'scm') {
+						//TODO - Shell should be separate flag
+						//  If user acquired admin access to CVS,
+						//  one to be given normal shell on CVS machine,
+						//  else - restricted.
+						//
+						$cvs_flags=$this->getVal('scm',0);
+						$sql="UPDATE user_group
+							SET cvs_flags=".$cvs_flags." 
+							WHERE user_id=".$user_id."
+							AND group_id='".$this->Group->getID()."'";
+						//echo '<h1>'.$cvs_flags.'::'.$sql.'</h1>';
+						$res2=db_query($sql);
+						if (!$res2) {
+							$this->setError('update::scm::'.db_error());
+							db_rollback();
+							return false;
+						}
+						// I have doubt the following is usefull
+						// This is probably buggy if used
+						if ($cvs_flags>1) {
+							if (!$SYS->sysUserSetAttribute($user_id,"debGforgeCvsShell","/bin/bash")) {
+								$this->setError($SYS->getErrorMessage());
+								db_rollback();
+								return false;
+							}
+						} else {
+							if (!$SYS->sysUserSetAttribute($user_id,"debGforgeCvsShell","/bin/cvssh")) {
+								$this->setError($SYS->getErrorMessage());
+								db_rollback();
+								return false;
+							}
+						}
+
+						//
+						//  If user acquired at least commit access to CVS,
+						//  one to be promoted to CVS group, else, demoted.
+						//  When we add the user we also check he has a shell as a group member
+						//  When we remove we only check for SCM (cvs_only=1)
+						//
+						if ($uvalue>0) {
+//echo "<h3>Role::setUser SYS->sysGroupAddUser(".$this->Group->getID().",$user_id,1)</h3>";
+							if (!$SYS->sysGroupAddUser($this->Group->getID(),$user_id,0)) {
+								$this->setError($SYS->getErrorMessage());
+								db_rollback();
+								return false;
+							}
+						} else {
+//echo "<h3>Role::setUser SYS->sysGroupRemoveUser(".$this->Group->getID().",$user_id,1)</h3>";
+							if (!$SYS->sysGroupRemoveUser($this->Group->getID(),$user_id,1)) {
+								$this->setError($SYS->getErrorMessage());
+								db_rollback();
+								return false;
+							}
+						}
+
+//
+//	If we decide to use a "RBAC Group" to define template roles
+//	The next 3 items will have to be modified to remap IDs for each project
+//
+
+					//
+					//	Forum
+					//
+					} elseif ($usection_name == 'forum') {
+						$sql="UPDATE forum_perm
+							SET perm_level='$uvalue'
+							WHERE
+							group_forum_id='$uref_id'
+							AND user_id='$user_id'";
+//echo "\n<br>$sql";
+						$res=db_query($sql);
+						if (!$res) {
+							$this->setError('update::forum::'.db_error());
+							db_rollback();
+							return false;
+						}
+					} elseif ($usection_name == 'pm') {
+						$sql="UPDATE project_perm
+							SET perm_level='$uvalue'
+							WHERE
+							group_project_id='$uref_id'
+							AND user_id='$user_id'";
+//echo "\n<br>$sql";
+						$res=db_query($sql);
+						if (!$res) {
+							$this->setError('update::pm::'.db_error());
+							db_rollback();
+							return false;
+						}
+					} elseif ($usection_name == 'tracker') {
+						$sql="UPDATE artifact_perm
+							SET perm_level='$uvalue'
+							WHERE
+							group_artifact_id='$uref_id'
+							AND user_id='$user_id'";
+//echo "\n<br>$sql";
+						$res=db_query($sql);
+						if (!$res) {
+							$this->setError('update::tracker::'.db_error());
+							db_rollback();
+							return false;
+						}
+
+					} elseif ($usection_name == 'docman') {
+						$update_usergroup=true;
+					} elseif ($usection_name == 'forumadmin') {
+						$update_usergroup=true;
+					} elseif ($usection_name == 'trackeradmin') {
+						$update_usergroup=true;
+					} elseif ($usection_name == 'projectadmin') {
+						$update_usergroup=true;
+					} elseif ($usection_name == 'pmadmin') {
+						$update_usergroup=true;
+					}
+	//			}
+			}
+		}
+	//	if ($update_usergroup) {
+			$sql="UPDATE user_group 
+				SET
+				admin_flags='".$this->getVal('projectadmin',0)."',
+				forum_flags='".$this->getVal('forumadmin',0)."',
+				project_flags='".$this->getVal('pmadmin',0)."',
+				doc_flags='".$this->getVal('docman',0)."',
+				cvs_flags='".$this->getVal('scm',0)."',
+				release_flags='".$this->getVal('frs',0)."',
+				artifact_flags='".$this->getVal('trackeradmin',0)."',
+				role_id='".$this->getID()."'
+				WHERE 
+				user_id='".$user_id."'
+				AND group_id='".$this->Group->getID()."'";
+//echo "\n<br>$sql";
+			$res=db_query($sql);
+			if (!$res) {
+				$this->setError('update::usergroup::'.db_error());
+				db_rollback();
+				return false;
+			}
+
+	//	}
+		db_commit();
+		return true;
+
+	}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/include/RoleObserver.class
===================================================================
--- trunk/gforge_base/gforge/common/include/RoleObserver.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/RoleObserver.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,331 @@
+<?php
+/**
+ * RoleObserver Class - this class handles the privacy settings
+ * for an entire project
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: RoleObserver.class 4420 2005-06-16 13:49:51Z vittal $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2004-03-16
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+class RoleObserver extends Error {
+
+	var $setting_array;
+	var $role_vals;
+	var $Group;
+	var $role_values=array(
+	'projectpublic'=>array('0','1'),
+	'scmpublic'=>array('0','1'),
+	'forumpublic'=>array('0','1'),
+	'forumanon'=>array('0','1'),
+	'trackerpublic'=>array('0','1'),
+	'trackeranon'=>array('0','1'),
+	'pmpublic'=>array('0','1'),
+	'frspackage'=>array('0','1'));
+
+	/**
+	 *  Role($group,$id) - CONSTRUCTOR.
+	 *
+	 *  @param  object	 The Group object.
+	 *  @param  int	 The role_id.
+	 */
+
+	function RoleObserver ($Group) {
+		$this->Error();
+		if (!$Group || !is_object($Group) || $Group->isError()) {
+			$this->setError('Role::'.$Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $Group;
+		return $this->fetchData();
+	}
+
+    /**
+     *  getID - get the ID of this role.
+     *
+     *  @return string The ID of the observer.
+     */
+	function getID() {
+		return 'observer';
+	}
+
+    /**
+     *  getName - get the name of this role.
+     *
+     *  @return string  The name of this role.
+     */
+	function getName() {
+		return 'Observer';
+	}
+
+	/**
+	 *  fetchData - May need to refresh database fields.
+	 *
+	 *  If an update occurred and you need to access the updated info.
+	 *
+	 *  @return boolean success;
+	 */
+	function fetchData() {
+		$this->setting_array=array();
+		//
+		//	Forum is_public/allow_anon
+		//
+		$res=db_query("SELECT group_forum_id,is_public,allow_anonymous 
+			FROM forum_group_list 
+			WHERE group_id='".$this->Group->getID()."'");
+		while ($arr =& db_fetch_array($res)) {
+			$this->setting_array['forumpublic'][$arr['group_forum_id']] = $arr['is_public'];
+			$this->setting_array['forumanon'][$arr['group_forum_id']] = $arr['allow_anonymous'];
+		}
+
+		//
+		//	Task Manager is_public/allow_anon
+		//
+		$res=db_query("SELECT group_project_id,is_public
+			FROM project_group_list 
+			WHERE group_id='".$this->Group->getID()."'");
+		while ($arr =& db_fetch_array($res)) {
+			$this->setting_array['pmpublic'][$arr['group_project_id']] = $arr['is_public'];
+		}
+
+		//
+		//	Tracker is_public/allow_anon
+		//
+		$res=db_query("SELECT group_artifact_id,is_public,allow_anon
+			FROM artifact_group_list 
+			WHERE group_id='".$this->Group->getID()."'");
+		while ($arr =& db_fetch_array($res)) {
+			$this->setting_array['trackerpublic'][$arr['group_artifact_id']] = $arr['is_public'];
+			$this->setting_array['trackeranon'][$arr['group_artifact_id']] = $arr['allow_anon'];
+		}
+
+		//
+		//	FRS packages can be public/private now
+		//
+		$res=db_query("SELECT package_id,is_public
+			FROM frs_package
+			WHERE group_id='".$this->Group->getID()."'");
+		while ($arr =& db_fetch_array($res)) {
+			$this->setting_array['frspackage'][$arr['package_id']] = $arr['is_public'];
+		}
+
+		//
+		//	AnonSCM
+		//
+		$this->Group->fetchData( $this->Group->getID() );
+		$this->setting_array['scmpublic'][0]=$this->Group->enableAnonSCM();
+		$this->setting_array['projectpublic'][0]=$this->Group->isPublic();
+//echo '<html><body><pre>'.print_r($this->setting_array).'</pre>';
+//exit;
+		return true;
+	}
+
+	/**
+	 *  &getRoleVals - get all the values and language text strings for this section.
+	 *
+	 *  @return array	Assoc array of values for this section.
+	 */
+	function &getRoleVals($section) {
+		global $Language,$role_vals;
+
+		//
+		//	Optimization - save array so it is only built once per page view
+		//
+		if (!isset($role_vals[$section])) {
+
+			for ($i=0; $i<count($this->role_values[$section]); $i++) {
+				//
+				//	Build an associative array of these key values + localized description
+				//
+				$role_vals[$section][$this->role_values[$section][$i]]=$Language->getText('rbac_vals',"$section".$this->role_values[$section][$i]);
+			}
+		}
+		return $role_vals[$section];
+	}
+
+    /**
+     *  getVal - get a value out of the array of settings for this role.
+     *
+     *  @param  string  The name of the role.
+     *  @param  integer The ref_id (ex: group_artifact_id, group_forum_id) for this item.
+     *  @return integer The value of this item.
+     */
+	function getVal($section,$ref_id) {
+		global $role_default_array;
+		if (!$ref_id) {
+			$ref_id=0;
+		}
+		if (!isset($this->setting_array) && !isset($this->data_array)) {
+			$this->setting_array=$role_default_array;
+		}
+		return $this->setting_array[$section][$ref_id];
+	}
+
+    /**
+     *  update - update a new in the database.
+     *
+     *  @param  array   A multi-dimensional array of data in this format: $data['section_name']['
+     *  @return boolean True on success or false on failure.
+     */
+	function update($data) {
+		$perm =& $this->Group->getPermission( session_get_user() );
+		if (!$perm || !is_object($perm) || $perm->isError() || !$perm->isAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		db_begin();
+
+////$data['section_name']['ref_id']=$val
+		$arr1 = array_keys($data);
+		for ($i=0; $i<count($arr1); $i++) {	
+			$arr2 = array_keys($data[$arr1[$i]]);
+			for ($j=0; $j<count($arr2); $j++) {
+				$usection_name=$arr1[$i];
+				$uref_id=$arr2[$j];
+				$uvalue=$data[$usection_name][$uref_id];
+				if (!$uref_id) {
+					$uref_id=0;
+				}
+				if (!$uvalue) {
+					$uvalue=0;
+				}
+				//
+				//	See if this setting changed. If so, then update it
+				//
+				if ($this->getVal($usection_name,$uref_id) != $uvalue) {
+					if ($usection_name == 'scmpublic' || 
+						$usection_name == 'projectpublic') {
+						if (!$data['scmpublic'][0]) {
+							$data['scmpublic'][0]=0;
+						}
+						if (!$data['projectpublic'][0]) {
+							$data['projectpublic'][0]=0;
+							// Groups cannot be private and have public SCM
+							// so we should always ensure that the scm is 
+							// private if we change a group to private.
+							$data['scmpublic'][0]=0;
+						}
+						$sql="UPDATE groups
+							SET
+							enable_anonscm='".$data['scmpublic'][0]."',
+							is_public='".$data['projectpublic'][0]."'
+							WHERE group_id='".$this->Group->getID()."'";
+							$res=db_query($sql);
+							if (!$res) {
+								$this->setError('update::group::'.db_error());
+								db_rollback();
+								return false;
+							}
+
+					//
+					//	Forum
+					//
+					} elseif ($usection_name == 'forumpublic' || $usection_name == 'forumanon') {
+						//
+						//	prevent double-updating each forum
+						//
+						if ($updated['forum'][$uref_id]) {
+							continue;
+						}
+						$sql="UPDATE forum_group_list
+							SET 
+							is_public='".$data['forumpublic'][$uref_id]."',
+							allow_anonymous='".$data['forumanon'][$uref_id]."'
+							WHERE
+							group_forum_id='$uref_id'
+							AND group_id='".$this->Group->getID()."'";
+//echo "\n<br>$sql";
+						$res=db_query($sql);
+						$updated['forum'][$uref_id]=1;
+						if (!$res) {
+							$this->setError('update::forum::'.db_error());
+							db_rollback();
+							return false;
+						}
+					} elseif ($usection_name == 'pmpublic') {
+
+						$sql="UPDATE project_group_list
+							SET 
+							is_public='$uvalue'
+							WHERE
+							group_project_id='$uref_id'
+							AND group_id='".$this->Group->getID()."'";
+//echo "\n<br>$sql";
+						$res=db_query($sql);
+						if (!$res) {
+							$this->setError('update::pm::'.db_error());
+							db_rollback();
+							return false;
+						}
+
+					} elseif ($usection_name == 'frspackage') {
+
+						$sql="UPDATE frs_package
+							SET 
+							is_public='$uvalue'
+							WHERE
+							package_id='$uref_id'
+							AND group_id='".$this->Group->getID()."'";
+//echo "\n<br>$sql";
+						$res=db_query($sql);
+						if (!$res) {
+							$this->setError('update::frspackage::'.db_error());
+							db_rollback();
+							return false;
+						}
+
+					} elseif ($usection_name == 'trackerpublic' || $usection_name == 'trackeranon') {
+						//
+						//	prevent double-updating each forum
+						//
+						if ($updated['tracker'][$uref_id]) {
+							continue;
+						}
+						$sql="UPDATE artifact_group_list
+							SET
+							is_public='".$data['trackerpublic'][$uref_id]."',
+							allow_anon='".$data['trackeranon'][$uref_id]."'
+							WHERE
+							group_artifact_id='$uref_id'
+							AND group_id='".$this->Group->getID()."'";
+//echo "\n<br>$sql";
+						$res=db_query($sql);
+						$updated['tracker'][$uref_id]=1;
+						if (!$res) {
+							$this->setError('update::tracker::'.db_error());
+							db_rollback();
+							return false;
+						}
+					}
+				}
+			}
+		}
+
+		db_commit();
+		$this->fetchData();
+		return true;
+	}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/include/SCM.class
===================================================================
--- trunk/gforge_base/gforge/common/include/SCM.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/SCM.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,51 @@
+<?php
+/**
+ *	SCM object
+ *
+ *	Provides an base class for an SCM plugin
+ *
+ * This file is copyright (c) Roland Mas <lolando at debian.org>, 2004
+ *
+ * $Id: SCM.class 3038 2004-05-16 16:54:59Z lo-lan-do $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/include/scm.php');
+
+class SCM extends Plugin {
+	/**
+	 * SCM() - constructor
+	 *
+	 */
+	function SCM () {
+		$this->Plugin() ;
+	}
+
+	function register () {
+		global $scm_list ;
+
+		$scm_list[] = $this->name ;
+	}
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/include/Stats.class
===================================================================
--- trunk/gforge_base/gforge/common/include/Stats.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/Stats.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,122 @@
+<?php   
+/**
+ *	Stats object
+ *
+ *	Provides access to various GForge statistics.
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+class Stats extends Error {
+
+	/**
+	 *	Stats - Stats object constructor
+	 */
+	function Stats() {
+		$this->Error();
+		return true;
+	}
+
+	/**
+	* Returns a resultset consisting of the month, day, total_users, pageviews, and sessions
+	* from the stats_site tables
+	*
+	* @return a resultset of month, day, total_users, pageviews, sessions
+	*/
+	function getSiteStats() {
+		$res = db_query("select byday.month,byday.day,byday.site_page_views as pageviews, ss.total_users, ss.sessions from stats_site_pages_by_day byday, stats_site ss where byday.month=ss.month and byday.day = ss.day order by byday.month asc, byday.day asc");
+		if (!$res) {
+			$this->setError('Unable to get stats: '.db_error());
+			return false;
+		}
+		return $res;
+	}
+
+	/**
+	* Returns a result set containing the group_name, unix_group_name, group_id, ranking, and percentile
+	* for either the last week or for all time
+	*
+	* @param type	week or null (for all time)
+	*	@param offset	used to page thru the result
+	* @return a resultset of group_name, unix_group_name, group_id, ranking, percentile
+	*/
+	function getMostActiveStats($type, $offset) {
+		$sql="";
+		if ($type == 'week') 	{
+		  $sql="SELECT groups.group_name,groups.unix_group_name,groups.group_id,project_weekly_metric.ranking,project_weekly_metric.percentile ".
+		    "FROM groups,project_weekly_metric ".
+		    "WHERE groups.group_id=project_weekly_metric.group_id AND ".
+		    "groups.is_public=1 ".
+		    "ORDER BY ranking ASC";
+		} else {
+			$sql="SELECT g.group_name,g.unix_group_name,g.group_id,s.group_ranking as ranking,s.group_metric as percentile ".
+		  "FROM groups g,stats_project_all_vw s  ".
+			"WHERE g.group_id=s.group_id ".
+			"AND g.is_public=1 and s.group_ranking > 0 ".
+			"ORDER BY ranking ASC";
+		}
+		return db_query($sql, $LIMIT, $offset);
+	}
+
+	/**
+	* Returns a resultset containing unix_group_name, group_name, and items - the count of
+	* the messages posted on that group's forums
+	*
+	* @return a resultset of unix_group_name, group_name, items
+	*/
+	function getTopMessagesPosted() {
+		return db_query("select g.unix_group_name, g.group_name, sum(s.msg_posted) as items "
+	." from stats_project s, groups g "
+	." where s.group_id = g.group_id "
+	." and g.is_public=1 "
+	." and g.status='A' "
+	." group by g.unix_group_name, g.group_name "
+	." order by items desc", 100);
+	}
+
+	/**
+	* Returns a resultset containing group_name, unix_group_name, and items - the count of
+	* the page views for that group
+	*
+	* @return a resultset of group_name, unix_group_name, items
+	*/
+	function getTopPageViews() {
+		return db_query("select g.group_name, g.unix_group_name, sum(s.page_views) as items "
+			." from stats_project_months s, groups g "
+			." where s.group_id = g.group_id "
+			." and g.is_public=1 "
+			." and g.status='A' "
+			." group by g.group_name, g.unix_group_name "
+			." order by items desc", 100);
+	}
+	
+	/**
+	* Returns a resultset containing group_name, unix_group_name, and items - the count of
+	* the downloads for that group
+	*
+	* @return a resultset of group_name, unix_group_name, items
+	*/
+	function getTopDownloads() {
+		return db_query("select g.group_name, g.unix_group_name, sum(frs.downloads) as items "
+			." from frs_dlstats_grouptotal_vw frs, groups g "
+			." where g.group_id = frs.group_id "
+			." and g.status='A' "
+			." and g.is_public='1' "
+			." group by g.group_name, g.unix_group_name "
+			." order by items desc", 100);
+	}
+}

Added: trunk/gforge_base/gforge/common/include/System.class
===================================================================
--- trunk/gforge_base/gforge/common/include/System.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/System.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,203 @@
+<?php
+/**
+ * System class
+ *
+ * Class to interact with the system
+ *
+ * @version   $Id: System.class 4002 2005-02-28 15:52:35Z cbayle $
+ * @author Christian Bayle
+ * @date 2004-02-05
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+class System extends Error {
+	/**
+	*	System()
+	*
+	*/
+	function System() {
+		$this->Error();
+		return true;
+	}
+
+	/*
+ 	* User management functions
+ 	*/
+
+	/**
+ 	* sysCheckUser() - Check for the existence of a user
+ 	* 
+ 	* @param		int		The user ID of the user to check
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysCheckUser($user_id) {
+		$user =& user_get_object($user_id);
+		if (!$user) {
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	/**
+ 	* sysCreateUser() - Create a user
+ 	*
+ 	* @param		int	The user ID of the user to create
+ 	* @returns The return status
+ 	*
+ 	*/
+	function sysCreateUser($user_id) {
+		$user = &user_get_object($user_id);
+		if (!$user) {
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	/**
+ 	* sysCheckCreateUser() - Check that a user has been created
+ 	*
+ 	* @param		int		The ID of the user to check
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysCheckCreateUser($user_id) {
+		return $this->sysCreateUser($user_id);
+	}
+
+	/**
+ 	* sysCheckCreateGroup() - Check that a group has been created
+ 	*
+ 	* @param		int		The ID of the user to check
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysCheckCreateGroup($user_id) {
+		return $this->sysCreateGroup($user_id);
+	}
+
+	/**
+ 	* sysRemoveUser() - Remove a user
+ 	*
+ 	* @param		int		The user ID of the user to remove
+ 	* @returns true on success/false on failure
+ 	*
+ 	*/
+	function sysRemoveUser($user_id) {
+		return true;
+	}
+
+	/**
+ 	* sysUserSetAttribute() - Set an attribute for a user
+ 	*
+ 	* @param		int		The user ID 
+ 	* @param		string	The attribute to set
+ 	* @param		string	The new value of the attribute
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysUserSetAttribute($user_id,$attr,$value) {
+		return true;
+	}
+
+	/*
+ 	* Group management functions
+ 	*/
+	
+	/**
+ 	* sysCheckGroup() - Check for the existence of a group
+ 	* 
+ 	* @param		int		The ID of the group to check
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysCheckGroup($group_id) {
+		return true;
+	}
+
+	/**
+ 	* sysCreateGroup() - Create a group
+ 	* 
+ 	* @param		int		The ID of the group to create
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysCreateGroup($group_id) {
+		return true;
+	}
+
+	/**
+ 	* sysRemoveGroup() - Remove a group
+ 	* 
+ 	* @param		int		The ID of the group to remove
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysRemoveGroup($group_id) {
+		return true;
+	}
+
+	/**
+ 	* sysGroupAddUser() - Add a user to a group
+ 	*
+ 	* @param		int		The ID of the group two which the user will be added
+ 	* @param		int		The ID of the user to add
+ 	* @param		bool	Only add this user to CVS
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysGroupAddUser($group_id,$user_id,$cvs_only=0) {
+		return true;
+	}
+
+	/**
+ 	* sysGroupRemoveUser() - Remove a user from a group
+ 	*
+ 	* @param		int		The ID of the group from which to remove the user
+ 	* @param		int		The ID of the user to remove
+ 	* @param		bool	Only remove user from CVS group
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysGroupRemoveUser($group_id,$user_id,$cvs_only=0) {
+		return true;
+	}
+	/**
+ 	* sysGroupUpdateUser() - Remove a user from a group
+ 	*
+ 	* @param		int		The ID of the group from which to remove the user
+ 	* @param		int		The ID of the user to remove
+ 	* @param		bool	Only remove user from CVS group
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysGroupUpdateUser($group_id,$user_id,$cvs_only=0) {
+		$this->sysGroupRemoveUser($group_id,$user_id,$cvs_only=0);
+		$this->sysGroupAddUser($group_id,$user_id,$cvs_only=0);
+		return true;
+	}
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/include/User.class
===================================================================
--- trunk/gforge_base/gforge/common/include/User.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/User.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,1457 @@
+<?php
+/**
+ * User class
+ *
+ * Sets up database results and preferences for a user and abstracts this info
+ *
+ *  You can now optionally pass in a db result
+ *  handle. If you do, it re-uses that query
+ *  to instantiate the objects
+ *
+ *  IMPORTANT! That db result must contain all fields
+ *  from users table or you will have problems
+ *
+ * GENERALLY YOU SHOULD NEVER INSTANTIATE THIS OBJECT DIRECTLY
+ * USE user_get_object() to instantiate properly - this will pool the objects
+ * and increase efficiency
+ *
+ * Copyright 1999-2001 (c) VA Linux Systems
+ *
+ * @version   $Id: User.class 5279 2006-02-09 16:33:46Z danper $
+ * @author Tim Perdue tperdue at valinux.com
+ * @date 2000-10-11
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+$USER_OBJ=array();
+
+/**
+ * user_get_object_by_name() - Get User object by username.
+ *  user_get_object is useful so you can pool user objects/save database queries
+ *  You should always use this instead of instantiating the object directly 
+ *
+ *  @param		string	The unix username - required
+ *  @param		int		The result set handle ("SELECT * FROM USERS WHERE user_id=xx")
+ *  @return a user object or false on failure
+ *
+ */
+function &user_get_object_by_name($user_name,$res=false) {
+	$user_name = strtolower($user_name);
+	if (!$res) {
+		$res=db_query("SELECT * FROM users WHERE user_name='$user_name'");
+	}
+	return user_get_object(db_result($res,0,'user_id'),$res);
+}
+
+/**
+ * user_get_object() - Get User object by user ID.
+ *  user_get_object is useful so you can pool user objects/save database queries
+ *  You should always use this instead of instantiating the object directly 
+ *
+ *  @param		int		The ID of the user - required
+ *  @param		int		The result set handle ("SELECT * FROM USERS WHERE user_id=xx")
+ *  @return a user object or false on failure
+ *
+ */
+function &user_get_object($user_id,$res=false) {
+	//create a common set of group objects
+	//saves a little wear on the database
+	
+	//automatically checks group_type and 
+	//returns appropriate object
+	
+	global $USER_OBJ;
+	if (!isset($USER_OBJ["_".$user_id."_"])) {
+		if ($res) {
+			//the db result handle was passed in
+		} else {
+			$res=db_query("SELECT * FROM users WHERE user_id='$user_id'");
+		}
+		if (!$res || db_numrows($res) < 1) {
+			$USER_OBJ["_".$user_id."_"]=false;
+		} else {
+			$USER_OBJ["_".$user_id."_"]= new User($user_id,$res);
+		}
+	}
+	return $USER_OBJ["_".$user_id."_"];
+}
+
+function &user_get_objects($id_arr) {
+	global $USER_OBJ;
+	$fetch = array();
+	$return = array();
+
+	for ($i=0; $i<count($id_arr); $i++) {
+		//
+		//  See if this ID already has been fetched in the cache
+		//
+		if (!$id_arr[$i]) {
+			continue;
+		}
+		if (!isset($USER_OBJ["_".$id_arr[$i]."_"])) {
+			$fetch[]=$id_arr[$i];
+		} else {
+			$return[] =& $USER_OBJ["_".$id_arr[$i]."_"];
+		}
+	}
+	if (count($fetch) > 0) {
+		$sql="SELECT * FROM users WHERE user_id IN ('".implode($fetch,'\',\'') ."')";
+		$res=db_query($sql);
+		while ($arr =& db_fetch_array($res)) {
+			$USER_OBJ["_".$arr['user_id']."_"] = new User($arr['user_id'],$arr);
+			$return[] =& $USER_OBJ["_".$arr['user_id']."_"];
+		}
+	}
+	return $return;
+}
+
+function &user_get_objects_by_name($username_arr) {
+	$res=db_query("SELECT user_id FROM users WHERE user_name IN ('".implode($username_arr,'\',\'')."')");
+	$arr =& util_result_column_to_array($res,0);
+	return user_get_objects($arr);
+}
+
+class User extends Error {
+	/** 
+	 * Associative array of data from db.
+	 *
+	 * @var		array	$data_array.
+	 */
+	var $data_array;
+	
+	/**
+	 * Is this person a site super-admin?
+	 *
+	 * @var		bool	$is_super_user
+	 */
+	var $is_super_user;
+
+	/**
+	 * Is this person the logged in user?
+	 *
+	 * @var		bool	$is_logged_in
+	 */
+	var $is_logged_in;
+
+	/**
+	 * Array of preferences
+	 *
+	 * @var		array	$user_pref
+	 */
+	var $user_pref;
+
+	var $theme;
+	var $theme_id;
+
+	/**
+	 *	User($id,$res) - CONSTRUCTOR - GENERALLY DON'T USE THIS
+	 *
+	 *	instead use the user_get_object() function call
+	 *
+	 *	@param	int		The user_id
+	 *	@param	int		The database result set OR array of data
+	 */
+	function User($id=false,$res=false) {
+		$this->Error();
+		if (!$id) {
+			//setting up an empty object
+			//probably going to call create()
+			return true;
+		}
+		if (!$res) {
+			$this->fetchData($id);
+		} else {
+			if (is_array($res)) {
+				$this->data_array =& $res;
+			} elseif (db_numrows($res) < 1) {
+				//function in class we extended
+				$this->setError('User Not Found');
+				$this->data_array=array();
+				return false;
+			} else {
+				//set up an associative array for use by other functions
+				db_reset_result($res);
+				$this->data_array =& db_fetch_array($res);
+			}
+		}
+		$this->is_super_user=false;
+		$this->is_logged_in=false;
+		return true;
+	}
+	
+	/**
+	 * create() - Create a new user.
+	 *
+	 * @param	string	The unix username.
+	 * @param	string	The real firstname.
+	 * @param	string	The real lastname.
+	 * @param	string	The first password.
+	 * @param	string	The confirmation password.
+	 * @param	string	The users email address.
+	 * @param	string	The users preferred default language.
+	 * @param	string	The users preferred default timezone.
+	 * @param	string	The users preference for receiving site updates by email.
+	 * @param	string	The users preference for receiving community updates by email.
+	 * @param	int		The ID of the language preference.
+	 * @param	string	The users preferred timezone.
+	 * @param	string	The users Jabber address.
+	 * @param	int		The users Jabber preference.
+	 * @param	int		The users theme_id.
+	 * @param	string	The users unix_box.
+	 * @param	string	The users address.
+	 * @param	string	The users address part 2.
+	 * @param	string	The users phone.
+	 * @param	string	The users fax.
+	 * @param	string	The users title.
+	 * @param	char(2)	The users ISO country_code.
+	 * @param	bool	Whether to send an email or not
+	 * @returns The newly created user ID
+	 *
+	 */
+	function create($unix_name,$firstname,$lastname,$password1,$password2,$email,
+		$mail_site,$mail_va,$language_id,$timezone,$jabber_address,$jabber_only,$theme_id,
+		$unix_box='shell',$address='',$address2='',$phone='',$fax='',$title='',$ccode='US',$send_mail=true) {
+		global $Language;
+		if (!$theme_id) {
+			$this->setError($Language->getText('account_register','err_themeid'));
+			return false;
+		}
+		if (!$unix_name) {
+			$this->setError($Language->getText('account_register','err_username'));
+			return false;
+		}
+		if (!$firstname) {
+			$this->setError($Language->getText('account_register','err_firstname'));
+			return false;
+		}
+		if (!$lastname) {
+			$this->setError($Language->getText('account_register','err_lastname'));
+			return false;
+		}
+		if (!$password1) {
+			$this->setError($Language->getText('account_register','err_passwd'));
+			return false;
+		}
+		if ($password1 != $password2) {
+			$this->setError($Language->getText('account_register','err_passwd2'));
+			return false;
+		}
+		if (!account_pwvalid($password1)) {
+			$this->setError($Language->getText('account_register','err_passwd3'));
+			return false;
+		}
+		$unix_name=strtolower($unix_name);
+		if (!account_namevalid($unix_name)) {
+			$this->setError($Language->getText('account_register','err_unixname'));
+			return false;
+		}
+		if (!validate_email($email)) {
+			$this->setError($Language->getText('account_register','err_email'));
+			return false;
+		}
+		if ($jabber_address && !validate_email($jabber_address)) {
+			$this->setError($Language->getText('account_register','err_jabber'));
+			return false;
+		}
+		if (!$jabber_only) {
+			$jabber_only=0;
+		} else {
+			$jabber_only=1;
+		}
+		if (db_numrows(db_query("SELECT user_id FROM users WHERE user_name LIKE '$unix_name'")) > 0) {
+			$this->setError($Language->getText('account_register','err_userexist'));
+			return false;
+		}
+		if ($GLOBALS['sys_require_unique_email']) {
+			if (db_numrows(db_query("SELECT user_id FROM users WHERE email='$email'")) > 0) {
+				$this->setError($Language->getText('account_register','err_mailexist'));
+				return false;
+			}
+		}
+		// if we got this far, it must be good
+		$confirm_hash = substr(md5($session_hash . $password1 . time()),0,16);
+		db_begin();
+		$sql="INSERT INTO users (user_name,user_pw,unix_pw,realname,firstname,lastname,email,add_date,
+			status,confirm_hash,mail_siteupdates,mail_va,language,timezone,jabber_address,jabber_only,
+			unix_box,address,address2,phone,fax,title,ccode,theme_id) 
+			VALUES ('$unix_name',
+			'". md5($password1) . "',
+			'". account_genunixpw($password1) . "',
+			'". htmlspecialchars($firstname.' '.$lastname). "',
+			'". htmlspecialchars($firstname). "',
+			'". htmlspecialchars($lastname). "',
+			'$email',
+			'" . time() . "',
+			'P',
+			'$confirm_hash',
+			'". (($mail_site)?"1":"0") . "',
+			'". (($mail_va)?"1":"0") . "',
+			'$language_id',
+			'$timezone',
+			'$jabber_address',
+			'$jabber_only',
+			'$unix_box',
+			'". htmlspecialchars($address) ."',
+			'". htmlspecialchars($address2) ."',
+			'". htmlspecialchars($phone) ."',
+			'". htmlspecialchars($fax) ."',
+			'". htmlspecialchars($title) ."',
+			'$ccode',
+			'$theme_id')";
+
+
+		$result=db_query($sql);
+	
+		if (!$result) {
+			$this->setError($Language->getText('account_register','err_badinsert') .db_error().$sql);
+			db_rollback();
+			return false;
+		} else {
+
+			$id = db_insertid($result,'users','user_id');
+			if (!$id) {
+				$this->setError('Could Not Get USERID: ' .db_error());
+				db_rollback();
+				return false;
+			}
+			// send mail
+			if (!$this->fetchData($id)) {
+				db_rollback();
+				return false;
+			}
+
+			if ($send_mail) {
+				$Language->loadLanguageID($language_id);
+				$this->sendRegistrationEmail();
+			}
+
+			db_commit();
+			return $id;
+		}
+	}
+
+	/**
+	 *	sendRegistrationEmail() - Send email for registration verification
+	 *
+	 *	@return true or false
+	 */
+	function sendRegistrationEmail() {
+		global $Language;
+		$message=stripcslashes($Language->getText('account_register','message_body',array($this->getUnixName(),$GLOBALS['sys_default_domain'],$this->getConfirmHash(),$GLOBALS['sys_name'])));
+		util_send_message(
+			$this->getEmail(),
+			$Language->getText('account_register','message_header',array($GLOBALS['sys_name'])),
+			$message
+		);
+	}
+
+	/**
+	 *	delete() - remove the User from all his groups.
+	 *
+	 *	Remove the User from all his groups and set his status to D.
+	 *
+	 *  @param	boolean	Confirmation of deletion.
+	 *	@return true or false
+	 */
+	function delete($sure) {
+		if (!$sure) {
+			return false;
+		} else {
+			$groups = &$this->getGroups();
+			if (is_array($groups)) {
+				foreach ($groups as $group) {
+					$group->removeUser($this->getID());
+				}
+			}
+
+			db_begin();
+			$res = db_query("DELETE FROM artifact_monitor WHERE user_id='".$this->getID()."' ");
+			if (!$res) {
+				$this->setError('ERROR - Could Not Delete From artifact_monitor: '.db_error());
+				db_rollback();
+				return false;
+			}
+			$res = db_query("DELETE FROM artifact_type_monitor WHERE user_id='".$this->getID()."' ");
+			if (!$res) {
+				$this->setError('ERROR - Could Not Delete From artifact_type_monitor: '.db_error());
+				db_rollback();
+				return false;
+			}
+			$res = db_query("DELETE FROM forum_monitored_forums WHERE user_id='".$this->getID()."' ");
+			if (!$res) {
+				$this->setError('ERROR - Could Not Delete From forum_monitored_forums: '.db_error());
+				db_rollback();
+				return false;
+			}				
+			$res = db_query("DELETE FROM filemodule_monitor WHERE user_id='".$this->getID()."' ");
+			if (!$res) {
+				$this->setError('ERROR - Could Not Delete From filemodule_monitor: '.db_error());
+				db_rollback();
+				return false;
+			}
+			$this->setStatus('D');
+			db_commit();
+		}
+		return true;
+	}
+
+	/**
+	 *	update() - update *common* properties of User object.
+	 *
+	 *	Use specific setter to change other properties.
+	 *
+	 *  @param	string	The users first name.
+	 *  @param	string	The users last name.
+	 *  @param	int		The ID of the users language preference.
+	 *  @param	string	The useres timezone preference.
+	 *  @param	string	The users preference for receiving site updates by email.
+	 *  @param	string	The users preference for receiving community updates by email.
+	 *	@param	string	The users preference for being participating in "peer ratings".
+	 *	@param	string	The users Jabber account address.
+	 *	@param	int	The users Jabber preference.
+	 *	@param	int	The users theme_id preference.
+	 *	@param	string	The users address.
+	 *	@param	string	The users address2.
+	 *	@param	string	The users phone.
+	 *	@param	string	The users fax.
+	 *	@param	string	The users title.
+	 *	@param	string	The users ccode.
+	 */
+	function update($firstname,$lastname,$language_id,$timezone,$mail_site,$mail_va,$use_ratings,
+		$jabber_address,$jabber_only,$theme_id,$address,$address2,$phone,$fax,$title,$ccode) {
+		global $Language;
+		$mail_site = $mail_site ? 1 : 0;
+		$mail_va   = $mail_va   ? 1 : 0;
+
+		if ($jabber_address && !validate_email($jabber_address)) {
+			$this->setError($Language->getText('account_register','err_jabber'));
+			return false;
+		}
+		if (!$jabber_only) {
+			$jabber_only=0;
+		} else {
+			$jabber_only=1;
+		}
+
+		db_begin();
+
+		$res = db_query("
+			UPDATE users
+			SET
+			realname='".htmlspecialchars($firstname . ' ' .$lastname)."',
+			firstname='".htmlspecialchars($firstname)."',
+			lastname='".htmlspecialchars($lastname)."',
+			language='$language_id',
+			timezone='$timezone',
+			mail_siteupdates=$mail_site,
+			mail_va=$mail_va,
+			jabber_address='$jabber_address',
+			jabber_only='$jabber_only',
+			address='". htmlspecialchars($address) ."',
+			address2='". htmlspecialchars($address2) ."',
+			phone='". htmlspecialchars($phone) ."',
+			fax='". htmlspecialchars($fax) ."',
+			title='". htmlspecialchars($title) ."',
+			ccode='$ccode',
+			theme_id='$theme_id'
+			WHERE user_id='".$this->getID()."'
+		");
+
+		if (!$res) {
+			$this->setError('ERROR - Could Not Update User Object: '.db_error());
+			db_rollback();
+			return false;
+		} else {
+			if (!$this->fetchData($this->getID())) {
+				db_rollback();
+				return false;
+			}
+			db_commit();
+			return true;
+		}
+	}
+
+	/**
+	 *	fetchData - May need to refresh database fields.
+	 *
+	 *	If an update occurred and you need to access the updated info.
+	 *
+	 *	@return boolean success;
+	 */
+	function fetchData($user_id) {
+		$res=db_query("SELECT * FROM users WHERE user_id='$user_id'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('User::fetchData()::'.db_error());
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		return true;
+	}
+	
+	/**
+	 *	getID - Simply return the user_id for this object.
+	 *
+	 *	@return	int	This user's user_id number.
+	 */
+	function getID() {
+		return $this->data_array['user_id'];
+	}
+
+	/**
+	 *	getStatus - get the status of this user.
+	 *
+	 *	Statuses include (A)ctive, (P)ending, (S)uspended ,(D)eleted.
+	 *
+	 *	@return	char	This user's status flag.
+	 */
+	function getStatus() {
+		return $this->data_array['status'];
+	}
+
+	/**
+	 *	setStatus - set this user's status.
+	 *
+	 *	@param	string	Status - P, A, S, or D.
+	 *	@return	boolean	success.
+	 */
+	function setStatus($status) {
+
+		if ($status != 'P' && $status != 'A'
+			&& $status != 'S' && $status != 'D') {
+			$this->setError('ERROR: Invalid status value');
+			return false;
+		}
+
+		db_begin();
+		$res=db_query("UPDATE users 
+			SET status='$status' 
+			WHERE user_id='". $this->getID()."'");
+
+		if (!$res) {
+			$this->setError('ERROR - Could Not Update User Status: '.db_error());
+			db_rollback();
+			return false;
+		} else {
+			$this->data_array['status']=$status;
+			if ($status == 'D') {
+				// Remove this user from all groups
+				$res = db_query(" DELETE FROM user_group WHERE user_id='".$this->getID()."' ");
+				if (!$res) {
+					$this->setError('ERROR - Could Not Propogate Deleted Status: '.db_error());
+					db_rollback();
+					return false;
+				}
+				$res=db_query("DELETE FROM forum_perm WHERE user_id='".$this->getID()."'");
+				if (!$res) {
+					$this->setError('ERROR - Could Not Propogate Deleted Status to forums: '.db_error());
+					db_rollback();
+					return false;
+				}
+				$res=db_query("DELETE FROM artifact_perm WHERE user_id='".$this->getID()."'");
+				if (!$res) {
+					$this->setError('ERROR - Could Not Propogate Deleted Status to tracker: '.db_error());
+					db_rollback();
+					return false;
+				}
+				$res=db_query("DELETE FROM project_perm WHERE user_id='".$this->getID()."'");
+				if (!$res) {
+					$this->setError('ERROR - Could Not Propogate Deleted Status to task manager: '.db_error());
+					db_rollback();
+					return false;
+				}
+			}
+			db_commit();
+			return true;
+		}
+	}
+
+	/**
+	 *	isActive - whether this user is confirmed and active.
+	 *
+	 *	Database field status of 'A' returns true.
+	 *	@return	boolean is_active.
+	 */
+	function isActive() {
+		if ($this->getStatus()=='A') {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 *	getUnixStatus - Status of activation of unix account.
+	 *
+	 *	@return	char	(N)one, (A)ctive, (S)uspended or (D)eleted
+	 */
+	function getUnixStatus() {
+		return $this->data_array['unix_status'];
+	}
+
+	/**
+	 *	setUnixStatus - Sets status of activation of unix account.
+	 *
+	 *	@param	string	The unix status.
+	 *	N	no_unix_account
+	 *	A	active
+	 *	S	suspended
+	 *	D	deleted
+	 *
+	 *	@return	boolean success.
+	 */
+	function setUnixStatus($status) {
+		global $Language,$SYS;
+		db_begin();
+		$res=db_query("
+			UPDATE users 
+			SET unix_status='$status' 
+			WHERE user_id='". $this->getID()."'
+		");
+
+		if (!$res) {
+			$this->setError('ERROR - Could Not Update User Unix Status: '.db_error());
+			db_rollback();
+			return false;
+		} else {
+			if ($status == 'A') {
+				if (!$SYS->sysCheckCreateUser($this->getID())) {
+					$this->setError($SYS->getErrorMessage());
+					db_rollback();
+					return false;
+				}
+			} else {
+				if ($SYS->sysCheckUser($this->getID())) {
+					if (!$SYS->sysRemoveUser($this->getID())) {
+						$this->setError($SYS->getErrorMessage());
+						db_rollback();
+						return false;
+					}
+				}
+			}
+			
+			$this->data_array['unix_status']=$status;
+			db_commit();
+			return true;
+		}
+	}
+
+	/**
+	 *	getUnixName - the user's unix_name.
+	 *
+	 *	@return	string	This user's unix/login name.
+	 */
+	function getUnixName() {
+		return strtolower($this->data_array['user_name']);
+	}
+
+	/**
+	 *	getUnixPasswd - get the user's password.
+	 *
+	 * 	@return	string	This user's unix crypted passwd.
+	 */
+	function getUnixPasswd() {
+		return $this->data_array['unix_pw'];
+	}
+
+	/**
+	 *	getUnixBox - the hostname of the unix box this user has an account on.
+	 *
+	 * 	@return	string	This user's shell login machine.
+	 */
+	function getUnixBox() {
+		return $this->data_array['unix_box'];
+	}
+
+	/**
+	 *	getMD5Passwd - the password.
+	 *
+	 *	@return	string	This user's MD5-crypted passwd.
+	 */
+	function getMD5Passwd() {
+		return $this->data_array['user_pw'];
+	}
+
+	/**
+	 *	getConfirmHash - the confirm hash in the db.
+	 *
+	 *	@return	string	This user's confirmation hash.
+	 */
+	function getConfirmHash() {
+		return $this->data_array['confirm_hash'];
+	}
+
+	/**
+	 *	getEmail - the user's email address.
+	 *
+	 *	@return	string	This user's email address.
+	 */
+	function getEmail() {
+		return $this->data_array['email'];
+	}
+
+	/**
+	 *	getNewEmail - while changing an email address, it is stored here until confirmation.
+	 *
+	 *	getNewEmail is a private operation for email change.
+	 *
+	 *	@return	string	This user's new (not yet confirmed) email address.
+	 *	@private
+	 */
+	function getNewEmail() {
+		return $this->data_array['email_new'];
+	}
+
+	/**
+	 *	setEmail - set a new email address, which must be confirmed.
+	 *
+	 *  @param	string	The email address.
+	 *	@return boolean success.
+	 */
+	function setEmail($email) {
+		if (!$email || !validate_email($email)) {
+			$this->setError('ERROR: Invalid Email');
+			return false;
+		}
+		$res=db_query("
+			UPDATE users 
+			SET email='$email' 
+			WHERE user_id='". $this->getID()."'
+		");
+
+		if (!$res) {
+			$this->setError('ERROR - Could Not Update User Email: '.db_error());
+			return false;
+		} else {
+			$this->data_array['email'] = $email;
+			return true;
+		}
+	}
+
+	/**
+	 *	setNewEmailAndHash - setNewEmailAndHash is a private operation for email change.
+	 *
+	 *  @param	string	The email address.
+	 *  @param	string	The email hash.
+	 *	@return boolean success.
+	 */
+	function setNewEmailAndHash($email, $hash='') {
+
+		if (!$hash) {
+			$hash = substr(md5(strval(time()) . strval(mt_rand())), 0, 16);
+		}
+
+		if (!$email || !validate_email($email)) {
+			$this->setError('ERROR - Invalid Email');
+			return false;
+		}
+
+		$res=db_query("
+			UPDATE users
+			SET confirm_hash='$hash',
+			email_new='$email'
+			WHERE user_id='".$this->getID()."'
+		");
+
+		if (!$res) {
+			$this->setError('ERROR - Could Not Update User Email And Hash: '.db_error());
+			return false;
+		} else {
+			$this->data_array['email_new']	= $email;
+			$this->data_array['confirm_hash'] = $hash;
+			return true;
+		}
+	}
+
+	/**
+	 *	getRealName - get the user's real name.
+	 *
+	 *	@return	string	This user's real name.
+	 */
+	function getRealName() {
+		return $this->getFirstName(). ' ' .$this->getLastName();
+	}
+
+	/**
+	 *	getFirstName - get the user's first name.
+	 *
+	 *	@return	string	This user's first name.
+	 */
+	function getFirstName() {
+		return $this->data_array['firstname'];
+	}
+
+	/**
+	 *	getLastName - get the user's last name.
+	 *
+	 *	@return	string	This user's last name.
+	 */
+	function getLastName() {
+		return $this->data_array['lastname'];
+	}
+
+	/**
+	 *	getAddDate - this user's unix time when account was opened.
+	 *
+	 *	@return	int	This user's unix time when account was opened.
+	 */
+	function getAddDate() {
+		return $this->data_array['add_date'];
+	}
+
+	/**
+	 *	getTimeZone - this user's timezone setting.
+	 *
+	 *	@return	string	This user's timezone setting.
+	 */
+	function getTimeZone() {
+		return $this->data_array['timezone'];
+	}
+
+	/**
+	 *	getCountryCode - this user's ccode setting.
+	 *
+	 *	@return	string	This user's ccode setting.
+	 */
+	function getCountryCode() {
+		return $this->data_array['ccode'];
+	}
+
+	/**
+	 *	getShell - this user's preferred shell.
+	 *
+	 *	@return	string	This user's preferred shell.
+	 */
+	function getShell() {
+		return $this->data_array['shell'];
+	}
+
+	/**
+	 *	setShell - sets user's preferred shell.
+	 *
+	 *  @param	string	The users preferred shell.
+	 *	@return boolean success.
+	 */
+	function setShell($shell) {
+		global $Language,$SYS;
+		$shells = file('/etc/shells');
+		$shells[count($shells)] = "/bin/cvssh";
+		$out_shells = array();
+		foreach ($shells as $s) {
+			if (substr($s, 0, 1) == '#') {
+				continue;
+			}
+			$out_shells[] = chop($s);
+		}
+		if (!in_array($shell, $out_shells)) {
+			$this->setError('ERROR: Invalid Shell');
+			return false;
+		}
+
+		db_begin();
+		$res=db_query("
+			UPDATE users 
+			SET shell='$shell' 
+			WHERE user_id='". $this->getID()."'
+		");
+
+		if (!$res) {
+			$this->setError('ERROR - Could Not Update User Unix Shell: '.db_error());
+			db_rollback();
+			return false;
+		} else {
+			// Now change LDAP attribute, but only if corresponding
+			// entry exists (i.e. if user have shell access)
+			if ($SYS->sysCheckUser($this->getID()))
+			{
+				if (!$SYS->sysUserSetAttribute($this->getID(),"loginShell",$shell)) {
+					$this->setError($SYS->getErrorMessage());
+					db_rollback();
+					return false;
+				}
+			}
+			$this->data_array['shell']=$shell;
+		}
+		db_commit();
+		return true;
+	}
+
+	/**
+	 *	getUnixUID() - Get the unix UID of the user
+	 *
+	 *	@return	int	This user's UID.
+	 */
+	function getUnixUID() {
+		return $this->data_array['unix_uid'];
+	}
+
+	/**
+	 *	getUnixGID() - Get the unix GID of the user
+	 *
+	 *	@return	int	This user's GID.
+	 */
+	function getUnixGID() {
+		return $this->data_array['unix_gid'];
+	}
+
+	/**
+	 *	getLanguage - this user's language_id from supported_languages table.
+	 *
+	 *	@return	int	This user's language_id.
+	 */
+	function getLanguage() {
+		return $this->data_array['language'];
+	}
+
+	/**
+	 *	getJabberAddress - this user's optional jabber address.
+	 *
+	 *	@return	string	This user's jabber address.
+	 */
+	function getJabberAddress() {
+		return $this->data_array['jabber_address'];
+	}
+
+	/**
+	 *	getJabberOnly - whether this person wants updates sent ONLY to jabber.
+	 *
+	 *	@return boolean	This user's jabber preference.
+	 */
+	function getJabberOnly() {
+		return $this->data_array['jabber_only'];
+	}
+
+	/**
+	 *	getAddress - get this user's address.
+	 *
+	 *	@return text	This user's address.
+	 */
+	function getAddress() {
+		return $this->data_array['address'];
+	}
+
+	/**
+	 *	getAddress2 - get this user's address2.
+	 *
+	 *	@return text	This user's address2.
+	 */
+	function getAddress2() {
+		return $this->data_array['address2'];
+	}
+
+	/**
+	 *	getPhone - get this person's phone number.
+	 *
+	 *	@return text	This user's phone number.
+	 */
+	function getPhone() {
+		return $this->data_array['phone'];
+	}
+
+	/**
+	 *	getFax - get this person's fax number.
+	 *
+	 *	@return text	This user's fax.
+	 */
+	function getFax() {
+		return $this->data_array['fax'];
+	}
+
+	/**
+	 *	getTitle - get this person's title.
+	 *
+	 *	@return text	This user's title.
+	 */
+	function getTitle() {
+		return $this->data_array['title'];
+	}
+
+	/**
+	 *	getGroups - get an array of groups this user is a member of.
+	 *
+	 *	@return array	Array of groups.
+	 */
+	function &getGroups() {
+		$sql="SELECT group_id
+			FROM user_group
+			WHERE user_id='". $this->getID() ."'";
+		$res=db_query($sql);
+		$arr =& util_result_column_to_array($res,0);	
+		return group_get_objects($arr);
+	}
+
+	/**
+	 *	getAuthorizedKeys - the SSH authorized keys set by the user.
+	 *
+	 *	@return	string	This user's SSH authorized (public) keys.
+	 */
+	function getAuthorizedKeys() {
+		return ereg_replace("###", "\n", $this->data_array['authorized_keys']);
+	}
+
+	/**
+	 *	setAuthorizedKeys - set the SSH authorized keys for the user.
+	 *
+	 *  @param	string	The users public keys.
+	 *	@return boolean success.
+	 */
+	function setAuthorizedKeys($keys) {
+		$keys = trim($keys);
+		$keys = ereg_replace("\r\n", "\n", $keys); // Convert to Unix EOL
+		$keys = ereg_replace("\n+", "\n", $keys); // Remove empty lines
+		$keys = ereg_replace("\n", "###", $keys); // Convert EOL to marker
+
+		$res=db_query("
+			UPDATE users 
+			SET authorized_keys='$keys'
+			WHERE user_id='".$this->getID()."'
+		");
+
+		if (!$res) {
+			$this->setError('ERROR - Could Not Update User SSH Keys');
+			return false;
+		} else {
+			$this->data_array['authorized_keys'] = $keys;
+			return true;
+		}
+	}
+
+	/**
+	 *	setLoggedIn($val) - Really only used by session code.
+	 *
+	 * 	@param	boolean	The session value.
+	 */
+	function setLoggedIn($val=true) {
+		$this->is_logged_in=$val;
+		if ($val) {
+			//if this is the logged in user, see if they are a super user
+			$sql="SELECT count(*) FROM user_group WHERE user_id='". $this->getID() ."' AND group_id='1' AND admin_flags='A'";
+			$result=db_query($sql);
+			if (!$result) {
+				$this->is_super_user=false;
+				return;
+			}
+			$row_count = db_fetch_array($result);
+			$this->is_super_user = ($row_count['count'] > 0);
+		}
+	}
+
+	/**
+	 *	isLoggedIn - only used by session code.
+	 *
+	 *	@return	boolean	is_logged_in.
+	 */
+	function isLoggedIn() {
+		return $this->is_logged_in;
+	}
+
+	/**
+	 *	deletePreference - delete a preference for this user.
+	 *
+	 *	@param	string	The unique field name for this preference.
+	 *	@return	boolean	success.
+	 */
+	function deletePreference($preference_name) {
+		$preference_name=strtolower(trim($preference_name));
+		unset($this->user_pref["$preference_name"]);
+		$res= db_query("DELETE FROM user_preferences 
+			WHERE user_id='". $this->getID() ."'
+			AND preference_name='$preference_name'");
+		return $res;
+	}
+
+	/**
+	 *	setPreference - set a new preference for this user.
+	 *
+	 *	@param	string	The unique field name for this preference.
+	 *	@param	string	The value you are setting this preference to.
+	 *	@return	boolean	success.
+	 */
+	function setPreference($preference_name,$value) {
+		$preference_name=strtolower(trim($preference_name));
+		//delete pref if not value passed in
+		unset($this->user_pref);
+		if (!isset($value)) {
+			$result=db_query("DELETE FROM user_preferences WHERE 
+				user_id='". $this->getID() ."' AND preference_name='$preference_name'");
+		} else {
+			$result=db_query("UPDATE user_preferences SET preference_value='$value',set_date='". time() ."' ".
+				"WHERE user_id='". $this->getID() ."' ".
+				"AND preference_name='$preference_name'");
+			if (db_affected_rows($result) < 1) {
+				//echo db_error();
+				$result=db_query("INSERT INTO user_preferences (user_id,preference_name,preference_value,set_date) ".
+					"VALUES ('". $this->getID() ."','$preference_name','$value','". time() ."')");
+				return $result;
+			}
+		}
+	}
+
+	/**
+	 *	getPreference - get a specific preference.
+	 *
+	 *	@param	string	The unique field name for this preference.
+	 *	@return the preference string or false on failure.
+	 */
+	function getPreference($preference_name) {
+		$preference_name=strtolower(trim($preference_name));
+		/*
+			First check to see if we have already fetched the preferences
+		*/
+		if ($this->user_pref) {
+			//echo "\n\nPrefs were fetched already";
+			if ($this->user_pref["$preference_name"]) {
+				//we have fetched prefs - return part of array
+				return $this->user_pref["$preference_name"];
+			} else {
+				//we have fetched prefs, but this pref hasn't been set
+				return false;
+			}
+		} else {
+			//we haven't returned prefs - go to the db
+			$result=db_query("SELECT preference_name,preference_value FROM user_preferences ".
+				"WHERE user_id='". $this->getID() ."'");
+			if (db_numrows($result) < 1) {
+				//echo "\n\nNo Prefs Found";
+				return false;
+			} else {
+				$pref=array();
+				//iterate and put the results into an array
+				for ($i=0; $i<db_numrows($result); $i++) {
+					$pref["".db_result($result,$i,'preference_name').""]=db_result($result,$i,'preference_value');
+				}
+				$this->user_pref = $pref;
+
+				if (array_key_exists($preference_name,$this->user_pref)) {
+					//we have fetched prefs - return part of array
+					return $this->user_pref["$preference_name"];
+				} else {
+					//we have fetched prefs, but this pref hasn't been set
+					return false;
+				}
+			}
+		}
+	}
+
+	/**
+	 *	setPasswd - Changes user's password.
+	 *
+	 *	@param	string	The plaintext password.
+	 *	@return boolean success.
+	 */
+	function setPasswd($passwd) {
+		global $Language,$SYS;
+		if (!account_pwvalid($passwd)) {
+			$this->setError('Error: '.$GLOBALS['register_error']);
+			return false;
+		}
+
+		db_begin();
+		$unix_pw = account_genunixpw($passwd);
+
+		$res=db_query("
+			UPDATE users
+			SET user_pw='" . md5($passwd) . "',
+			unix_pw='$unix_pw'
+			WHERE user_id='".$this->getID()."'
+		");
+
+		if (!$res || db_affected_rows($res) < 1) {
+			$this->setError('ERROR - Could Not Change User Password: '.db_error());
+			db_rollback();
+			return false;
+		} else {
+			// Now change LDAP password, but only if corresponding
+			// entry exists (i.e. if user have shell access)
+			if ($SYS->sysCheckUser($this->getID())) {
+				if (!$SYS->sysUserSetAttribute($this->getID(),"userPassword",'{crypt}'.$unix_pw)) {
+					$this->setError($SYS->getErrorMessage());
+					db_rollback();
+					return false;
+				}
+			}
+		}
+		db_commit();
+		return true;
+	}
+
+	/**
+	 *  getPlugins -  get a list of all available user plugins
+	 *
+	 *  @return array array containing plugin_id => plugin_name
+	 */
+	function getPlugins() {
+		if (!isset($this->plugins_data)) {
+			$this->plugins_data = array () ;
+			$sql="SELECT user_plugin.plugin_id, plugins.plugin_name
+				FROM user_plugin, plugins
+				WHERE user_plugin.user_id=".$this->getID()."
+					AND user_plugin.plugin_id = plugins.plugin_id" ;
+			$res=db_query($sql);
+			$rows = db_numrows($res);
+
+			for ($i=0; $i<$rows; $i++) {
+				$plugin_id = db_result($res,$i,'plugin_id');
+				$this->plugins_data[$plugin_id] = db_result($res,$i,'plugin_name');
+			}
+		}
+		return $this->plugins_data ;
+	}
+
+	/**
+	 *  usesPlugin - returns true if the user uses a particular plugin 
+	 *
+	 *  @param	string	name of the plugin
+	 *  @return	boolean	whether plugin is being used or not
+	 */
+	function usesPlugin($pluginname) {
+		$plugins_data = $this->getPlugins() ;
+		foreach ($plugins_data as $p_name) {
+			if ($p_name == $pluginname) {
+				return true ;
+			}
+		}
+		return false ;
+	}
+
+	/**
+	 *  setPluginUse - enables/disables plugins for the user
+	 *
+	 *  @param	string	name of the plugin
+	 *  @param	boolean	the new state
+	 *  @return	string	database result
+	 */
+	function setPluginUse($pluginname, $val=true) {
+		if ($val == $this->usesPlugin($pluginname)) {
+			// State is already good, returning
+			return true ;
+		}
+		$sql="SELECT plugin_id
+			FROM plugins
+			WHERE plugin_name = '" . $pluginname . "'" ;
+		$res=db_query($sql);
+		$rows = db_numrows($res);
+		if ($rows == 0) {
+			// Error: no plugin by that name
+			return false ;
+		}
+		$plugin_id = db_result($res,0,'plugin_id');
+		// Invalidate cache
+		unset ($this->plugins_data) ;
+		if ($val) {
+			$sql="INSERT INTO user_plugin (user_id, plugin_id)
+				VALUES (". $this->getID() . ", ". $plugin_id .")" ;
+			$res=db_query($sql);
+			return $res ;
+		} else {
+			$sql="DELETE FROM user_plugin
+				WHERE user_id = ". $this->getID() . "
+				AND plugin_id = ". $plugin_id ;
+			$res=db_query($sql);
+			return $res ;
+		}
+	}
+
+	/**
+	 *	getMailingsPrefs - Get activity status for one of the site mailings.
+	 *
+	 *	@param	string	The id of mailing ('mail_va' for community mailings, 'mail_siteupdates' for site mailings)
+	 *	@return	boolean success.
+	 */
+	function getMailingsPrefs($mailing_id) {
+		if ($mailing_id=='va') {
+			return $this->data_array['mail_va'];
+		} else if ($mailing_id=='site') {
+			return $this->data_array['mail_siteupdates'];
+		} else {
+			return 0;
+		}
+	}
+
+	/**
+	 *	unsubscribeFromMailings - Disable email notifications for user.
+	 *
+	 *	@param	boolean	If false, disable general site mailings, else - all.
+	 *	@return	boolean	success.
+	 */
+	function unsubscribeFromMailings($all=false) {
+		$res1 = $res2 = $res3 = true;
+		$res1 = db_query("
+			UPDATE users
+			SET mail_siteupdates=0,
+				mail_va=0
+			WHERE user_id='".$this->getID()."'
+		");
+		if ($all) {
+			$res2 = db_query("
+				DELETE FROM forum_monitored_forums
+				WHERE user_id='".$this->getID()."'
+			");
+			$res3 = db_query("
+				DELETE FROM filemodule_monitor
+				WHERE user_id='".$this->getID()."'
+			");
+		}
+
+		return $res1 && $res2 && $res3;
+	}
+
+	/**
+	 *	getThemeID - get the theme_id for this user.
+	 *
+	 *	@return	int	The theme_id.
+	 */
+	function getThemeID() {
+		return $this->data_array['theme_id'];
+	}
+
+	/**
+	 *	getThemeID - get the theme_id for this user from the theme_prefs table.
+	 *
+	 *	@return	int	The theme_id.
+	 */
+	function setUpTheme() {
+//
+//	An optimization in session_getdata lets us pre-fetch this in most cases.....
+//
+		if (!$this->data_array['dirname']) {
+			$res=db_query("SELECT dirname FROM themes WHERE theme_id='".$this->getThemeID()."'");
+			$this->theme=db_result($res,0,'dirname');
+		} else {
+			$this->theme=$this->data_array['dirname'];
+		}
+		if (is_file($GLOBALS['sys_themeroot'].$this->theme.'/Theme.class')) {
+			$GLOBALS['sys_theme']=$this->theme;
+		} else {
+			$this->theme=$GLOBALS['sys_theme'];
+		}
+		return $this->theme;
+	}
+}
+
+/*
+
+
+
+
+		EVERYTHING BELOW HERE IS DEPRECATED
+
+
+		DO NOT USE FOR ANY NEW CODE
+
+
+
+*/
+
+
+
+/**
+ * user_ismember() - DEPRECATED; DO NOT USE!
+ *
+ * @param		int		The Group ID
+ * @param		int		The Type
+ * @deprecated
+ *
+ */
+function user_ismember($group_id,$type=0) {
+	if (!session_loggedin()) {
+		return false;
+	}
+
+	$project =& group_get_object($group_id);
+
+	if (!$project || !is_object($project)) {
+			return false;
+	}
+
+	$perm =& $project->getPermission( session_get_user() );
+	if (!$perm || !is_object($perm) || !$perm->isMember()) {
+		return false;
+	}
+
+	$type=strtoupper($type);
+	
+	switch ($type) {
+		case 'P2' : {
+			//pm admin
+			return $perm->isPMAdmin();
+			break; 
+		}
+		case 'F2' : {
+			//forum admin
+			return $perm->isForumAdmin();
+			break; 
+		}
+		case '0' : {
+			//just in this group
+			return $perm->isMember();
+			break;
+		}
+		case 'A' : {
+			//admin for this group
+			return $perm->isAdmin();
+			break;
+		}
+		case 'D1' : {
+			//document editor
+			return $perm->isDocEditor();
+			break;
+		}
+		default : {
+			//fubar request
+			return false;
+		}
+	}
+	return false;
+}
+
+/**
+ * user_getname() - DEPRECATED; DO NOT USE!
+ *
+ * @param		int		The User ID
+ * @deprecated
+ *
+ */
+function user_getname($user_id = false) {
+	// use current user if one is not passed in
+	if (!$user_id) {
+		if (session_loggedin()) {
+			$user=&user_get_object(user_getid());
+			if ($user) {
+				return $user->getUnixName();
+			} else {
+				return 'Error getting user';
+			}
+		} else {
+			return 'No User Id';
+		}
+	} else {
+		$user=&user_get_object($user_id);
+		if ($user) {
+			return $user->getUnixName();
+		} else {
+			return 'Invalid User';
+		}
+	}
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/include/Validator.class
===================================================================
--- trunk/gforge_base/gforge/common/include/Validator.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/Validator.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,75 @@
+<?php
+/**
+ * GForge Project Management Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+
+/**
+* This class is a simple utility to validate fields
+* 
+* Sample usage:
+*
+* $v = new Validator();
+* $v->check($summary, "summary");
+* $v->check($detail, "detail");
+* if (!$v->isClean()) {
+*  print $v->formErrorMsg("The following fields were null:");
+* }
+*
+*/
+class Validator {
+	var $badfields;
+
+	/**
+	* Checks to see if a field is null; if so, the field name is added to an internal array
+	*
+	* @param field - a variable to check for null
+	* @param name - the variable name
+	*/
+	function check($field, $name) {
+		if (!$field) {
+			$this->badfields[] = $name;
+		}
+	}
+
+	/**
+	* Returns true if no null fields have been checked so far
+	*
+	* @return boolean - True if there are no null fields so far
+	*/
+	function isClean() {
+		return count($this->badfields) == 0;
+	}
+	
+	/**
+	* Returns an error message which contains the null field names which have been checked
+	*
+	* @param preamble string - A string with which to start the error message
+	* @return string - A complete error message
+	*/
+	function formErrorMsg($preamble) {
+		foreach ($this->badfields as $field) {
+			$preamble = $preamble.$field.",";
+		}
+		return substr($preamble, 0, strlen($preamble)-1);
+	}
+}
+?>

Added: trunk/gforge_base/gforge/common/include/account.php
===================================================================
--- trunk/gforge_base/gforge/common/include/account.php	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/account.php	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,218 @@
+<?php
+/**
+ * account.php - A library of common account management functions.
+ *
+ * Copyright 1999-2001 (c) VA Linux Systems
+ *
+ * @version   $Id: account.php 3112 2004-07-21 20:39:34Z cbayle $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/**
+ * account_pwvalid() - Validates a password
+ *
+ * @param		string	The plaintext password string
+ * @returns		true on success/false on failure
+ *
+ */
+function account_pwvalid($pw) {
+	global $Language;
+	if (strlen($pw) < 6) {
+		$GLOBALS['register_error'] = $Language->getText('common_include_account','sixchar');
+		return 0;
+	}
+	return 1;
+}
+
+/**
+ * account_namevalid() - Validates a login username
+ *
+ * @param		string	The username string
+ * @returns		true on success/false on failure
+ *
+ */
+function account_namevalid($name) {
+	global $Language;
+	// no spaces
+	if (strrpos($name,' ') > 0) {
+		$GLOBALS['register_error'] = $Language->getText('common_include_account','nospace');
+		return 0;
+	}
+
+	// min and max length
+	if (strlen($name) < 3) {
+		$GLOBALS['register_error'] = $Language->getText('common_include_account','tooshort');
+		return 0;
+	}
+	if (strlen($name) > 15) {
+		$GLOBALS['register_error'] = $Language->getText('common_include_account','toolong');
+		return 0;
+	}
+
+	if (!ereg('^[a-z][-a-z0-9_]+$', $name)) {
+		$GLOBALS['register_error'] = $Language->getText('common_include_account','illegal');
+		return 0;
+	}
+
+	// illegal names
+	if (eregi("^((root)|(bin)|(daemon)|(adm)|(lp)|(sync)|(shutdown)|(halt)|(mail)|(news)"
+		. "|(uucp)|(operator)|(games)|(mysql)|(httpd)|(nobody)|(dummy)"
+		. "|(www)|(cvs)|(shell)|(ftp)|(irc)|(debian)|(ns)|(download))$",$name)) {
+		$GLOBALS['register_error'] = "Name is reserved.";
+		return 0;
+	}
+	if ( exec("getent passwd $name") != "" ){
+		$GLOBALS['register_error'] = $Language->getText('account_register','err_userexist');
+		return 0;
+	}
+	if (eregi("^(anoncvs_)",$name)) {
+		$GLOBALS['register_error'] = $Language->getText('common_include_account','cvsreserved');
+		return 0;
+	}
+		
+	return 1;
+}
+
+/**
+ * account_groupnamevalid() - Validates an account group name
+ *
+ * @param		string	The group name string
+ * @returns		true on success/false on failure
+ *
+ */
+function account_groupnamevalid($name) {
+	global $Language;
+	if (!account_namevalid($name)) return 0;
+	
+	// illegal names
+	if (eregi("^((www[0-9]?)|(cvs[0-9]?)|(shell[0-9]?)|(ftp[0-9]?)|(irc[0-9]?)|(news[0-9]?)"
+		. "|(mail[0-9]?)|(ns[0-9]?)|(download[0-9]?)|(pub)|(users)|(compile)|(lists)"
+		. "|(slayer)|(orbital)|(tokyojoe)|(webdev)|(projects)|(cvs)|(slayer)|(monitor)|(mirrors?))$",$name)) {
+		$GLOBALS['register_error'] = $Language->getText('common_include_account','dnsreserved');
+		return 0;
+	}
+
+	if (eregi("_",$name)) {
+		$GLOBALS['register_error'] = $Language->getText('common_include_account','nounderscore');
+		return 0;
+	}
+
+	return 1;
+}
+
+/**
+ * rannum() - Generate a random number
+ * 
+ * This is a local function used for account_salt()
+ *
+ * @return int $num A random number
+ *
+ */
+function rannum(){	     
+	mt_srand((double)microtime()*1000000);		  
+	$num = mt_rand(46,122);		  
+	return $num;		  
+}	     
+
+/**
+ * genchr() - Generate a random character
+ * 
+ * This is a local function used for account_salt()
+ *
+ * @return int $num A random character
+ *
+ */
+function genchr(){
+	do {	  
+		$num = rannum();		  
+	} while ( ( $num > 57 && $num < 65 ) || ( $num > 90 && $num < 97 ) );	  
+	$char = chr($num);	  
+	return $char;	  
+}	   
+
+/**
+ * account_gensalt() - A random salt generator
+ *
+ * @returns The random salt string
+ *
+ */
+function account_gensalt(){
+
+	$a = genchr(); 
+	$b = genchr();
+	$salt = "$1$" . "$a$b";
+	return $salt;	
+}
+
+/**
+ * account_genunixpw() - Generate unix password
+ *
+ * @param		string	The plaintext password string
+ * @return		The encrypted password
+ *
+ */
+function account_genunixpw($plainpw) {
+	return crypt($plainpw,account_gensalt());
+}
+
+/**
+ * account_shellselects() - Print out shell selects
+ *
+ * @param		string	The current shell
+ *
+ */
+function account_shellselects($current) {
+	$shells = file("/etc/shells");
+	$shells[count($shells)] = "/bin/cvssh";
+
+	for ($i = 0; $i < count($shells); $i++) {
+		$this_shell = chop($shells[$i]);
+
+		if ($current == $this_shell) {
+			echo "<option selected value=$this_shell>$this_shell</option>\n";
+		} else {
+			if (! ereg("^#",$this_shell)){
+				echo "<option value=$this_shell>$this_shell</option>\n";
+			}
+		}
+	}
+}
+
+/**
+ *	account_user_homedir() - Returns full path of user home directory
+ *
+ *  @param		string	The username
+ *	@return home directory path
+ */
+function account_user_homedir($user) {
+	//return '/home/users/'.substr($user,0,1).'/'.substr($user,0,2).'/'.$user;
+	return $GLOBALS['homedir_prefix'].'/'.$user;
+}
+
+/**
+ *	account_group_homedir() - Returns full path of group home directory
+ *
+ *  @param		string	The group name
+ *	@return home directory path
+ */
+function account_group_homedir($group) {
+	//return '/home/groups/'.substr($group,0,1).'/'.substr($group,0,2).'/'.$group;
+	return $GLOBALS['groupdir_prefix'].'/'.$group;
+}
+
+?>

Added: trunk/gforge_base/gforge/common/include/constants.php
===================================================================
--- trunk/gforge_base/gforge/common/include/constants.php	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/constants.php	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,53 @@
+<?php
+
+/* Search */
+
+define('SEARCH__TYPE_IS_ARTIFACT', 'artifact');
+define('SEARCH__TYPE_IS_SOFTWARE', 'soft');
+define('SEARCH__TYPE_IS_FORUM', 'forum');
+define('SEARCH__TYPE_IS_PEOPLE', 'people');
+define('SEARCH__TYPE_IS_SKILL', 'skill');
+define('SEARCH__TYPE_IS_DOCS', 'docs');
+define('SEARCH__TYPE_IS_TRACKERS', 'trackers');
+define('SEARCH__TYPE_IS_TASKS', 'tasks');
+define('SEARCH__TYPE_IS_FORUMS', 'forums');
+define('SEARCH__TYPE_IS_NEWS', 'news');
+define('SEARCH__TYPE_IS_FRS', 'frs');
+define('SEARCH__TYPE_IS_FULL_PROJECT', 'full');
+
+define('SEARCH__DEFAULT_ROWS_PER_PAGE', 25);
+define('SEARCH__ALL_SECTIONS', 'all');
+
+define('SEARCH__PARAMETER_GROUP_ID', 'group_id');
+define('SEARCH__PARAMETER_ARTIFACT_ID', 'atid');
+define('SEARCH__PARAMETER_FORUM_ID', 'forum_id');
+define('SEARCH__PARAMETER_GROUP_PROJECT_ID', 'group_project_id');
+
+define('SEARCH__OUTPUT_RSS', 'rss');
+define('SEARCH__OUTPUT_HTML', 'html');
+
+define('SEARCH__MODE_OR', 'or');
+define('SEARCH__MODE_AND', 'and');
+
+/* Mailing lists */
+
+define('MAIL__MAILING_LIST_IS_PRIVATE', '0');
+define('MAIL__MAILING_LIST_IS_PUBLIC', '1');
+define('MAIL__MAILING_LIST_IS_DELETED', '9');
+
+define('MAIL__MAILING_LIST_IS_REQUESTED', '1');
+define('MAIL__MAILING_LIST_IS_CREATED', '2');
+
+define('MAIL__MAILING_LIST_NAME_MIN_LENGTH', 4);
+
+/* Groups */
+
+define('GROUP_IS_MASTER', 1);
+define('GROUP_IS_NEWS', 3);
+define('GROUP_IS_STATS', 2);
+define('GROUP_IS_PEER_RATINGS', 4);
+
+/* Admin */
+define('ADMIN_CRONMAN_ROWS', 30);
+
+?>
\ No newline at end of file

Added: trunk/gforge_base/gforge/common/include/cron_utils.php
===================================================================
--- trunk/gforge_base/gforge/common/include/cron_utils.php	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/cron_utils.php	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,129 @@
+<?php
+
+//
+//	This key id# is important - do not change or renumber
+//
+$cron_arr = array();
+$cron_arr[0]='unused';
+$cron_arr[1]='calculate_user_metric.php';
+$cron_arr[2]='check_stale_tracker_items.php';
+$cron_arr[3]='db_project_sums.php';
+$cron_arr[4]='db_stats_agg.php';
+$cron_arr[5]='db_trove_maint.php';
+$cron_arr[6]='massmail.php';
+$cron_arr[7]='project_cleanup.php';
+$cron_arr[8]='project_weekly_metric.php';
+$cron_arr[9]='rating_stats.php';
+$cron_arr[10]='rotate_activity.php';
+$cron_arr[11]='site_stats.php';
+$cron_arr[12]='vacuum.php';
+$cron_arr[13]='cvs.php';
+$cron_arr[14]='history_parse.php';
+$cron_arr[15]='ssh_create.php';
+$cron_arr[16]='usergroup.php';
+$cron_arr[17]='mailaliases.php';
+$cron_arr[18]='mailing_lists_create.php';
+$cron_arr[19]='tarballs.php';
+$cron_arr[20]='reporting_cron.php';
+$cron_arr[21]='create_svn.php';
+$cron_arr[22]='daily_task_email.php';
+$cron_arr[24]='svn-stats.php';
+
+function cron_entry($job,$output) {
+	$sql="INSERT INTO cron_history (rundate,job,output) 
+		values ('".time()."','$job','$output')";
+	return db_query($sql);
+}
+
+function cron_debug($string) {
+	global $verbose;
+	if($verbose) {
+		echo $string."\n";
+	}
+}
+
+function checkChroot() {
+	global $sys_chroot;
+	if(isset($sys_chroot) && !empty($sys_chroot) && is_dir($sys_chroot)) {
+		return true;
+	}
+	return false;
+}
+
+function chrootPath($path) {
+	global $sys_chroot;
+	if(checkChroot()) {
+		$path = $sys_chroot.$path;
+	}
+	return $path;
+}
+
+function chrootCommand($command) {
+	global $sys_chroot;
+	if(checkChroot()) {
+		$command = 'chroot '.$sys_chroot.' '.$command;
+	}
+	return $command;
+}
+
+//
+//  Create lock file so long running jobs don't overlap
+//
+//  Parameters
+//  $name - Name of cron job to use in the lock file name
+//
+//	Return code
+//  true - lock file create successfully
+//  false - file already exists
+//	IMPORTANT - There tmp dir should have write access to create the lock
+function cron_create_lock($name) {
+	if (!preg_match('/^[[:alnum:]\.\-_]+$/', $name)) {
+		return false;
+	}
+	$lockf = '/tmp/blahlock'.$name;
+
+	if (file_exists($lockf)) {
+		return false;
+	} else {
+	    $fp = fopen($lockf,'w');
+		if ($fp) {
+			fclose($fp);
+			return true;
+		}
+	}
+	return false;
+}
+
+//
+//  Delete lock file created by cron_create_lock
+//
+//  Parameters
+//  $name - Name of cron job to use in the lock file name
+//
+//	Return code
+//  true - lock file deleted successfully
+//  false - error deleting file
+function cron_remove_lock($name) {
+	$lockf = '/tmp/blahlock'.$name;
+
+	if (file_exists($lockf) && is_writeable($lockf)) {
+		if (unlink($lockf)) {
+			return true;
+		}
+	}
+	return false;
+}
+
+//
+//  Set up this script to run as the site admin
+//
+function runCronAsSiteAdmin() {
+	$res = db_query("SELECT user_id FROM user_group WHERE admin_flags='A' AND group_id='1'");
+	if (!$res || db_numrows($res) == 0) {
+		return false;
+	}
+	$id=db_result($res,0,0);
+	session_set_new($id);
+}
+
+?>
\ No newline at end of file

Added: trunk/gforge_base/gforge/common/include/database.php
===================================================================
--- trunk/gforge_base/gforge/common/include/database.php	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/database.php	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,367 @@
+<?php
+/**
+ * database.php - The database abstraction library
+ * This is the PostgreSQL version of our database connection/querying layer
+ *
+ * Copyright 1999-2001 (c) VA Linux Systems
+ *
+ * @version   $Id: database.php 4424 2005-06-17 13:32:24Z marcelo $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+//$conn - database connection handle
+
+/**
+ * Current row for each result set
+ *
+ * @var			array	$sys_db_row_pointer
+ */
+$sys_db_row_pointer=array(); //current row for each result set
+
+/**
+ * pg_connectstring() - builds a postgres connection string.
+ * Combines the supplied arguments into a valid, specific, postgresql
+ * connection string. It only includes the host and port options
+ * if specified. Without those options, it will use the unix domain
+ * sockets to connect to the postgres server on the local machine.
+ *
+ * @author	Graham Batty graham at sandworm.ca
+ * @param	dbname		The database to connect to. Required.
+ * @param	user		The username used to connect. Required
+ * @param	password	The password used to connect
+ * @param	host		The hostname to connect to, if not localhost
+ * @param	port		The port to connect to, if not 5432
+ * @return	string		The connection string to pass to pg_connect()
+ * @date	2003-02-12
+ */
+function pg_connectstring($dbname, $user, $password = "", $host = "", $port = "") {
+	$string = "dbname=$dbname user=$user";
+	if ($password != "")
+		$string .= " password=$password";
+	if ($host != "") {
+		$string .= " host=$host";
+		if ($port != "")
+			$string .= " port=$port";
+	}
+	return $string;
+}
+
+
+/**
+ *  db_connect() - Connect to the database
+ *  Notice the global vars that must be set up
+ *  Sets up a global $conn variable which is used 
+ *  in other functions in this library.
+ */
+function db_connect() {
+	global $sys_dbhost,$sys_dbuser,$sys_dbpasswd,$conn,
+		$sys_dbname,$sys_db_use_replication,$sys_dbport,$sys_dbreaddb,$sys_dbreadhost;
+
+	//
+	//	Connect to primary database
+	//
+	$conn = @pg_pconnect(pg_connectstring($sys_dbname, $sys_dbuser, $sys_dbpasswd, $sys_dbhost, $sys_dbport));
+
+	//
+	//	If any replication is configured, connect
+	//
+	if ($sys_db_use_replication) {
+		$conn2 = @pg_pconnect(pg_connectstring($sys_dbreaddb, $sys_dbuser, $sys_dbpasswd, $sys_dbreadhost, $sys_dbreadport));
+	} else {
+		$conn2 = $conn;
+	}
+
+	//
+	//	Now map the physical database connections to the
+	//	"virtual" list that is used to distribute load in db_query()
+	//
+	define('SYS_DB_PRIMARY', $conn);
+	define('SYS_DB_STATS', $conn2);
+	define('SYS_DB_TROVE', $conn2);
+	define('SYS_DB_SEARCH', $conn2);
+
+	// Register top-level "finally" handler to abort current
+	// transaction in case of error
+	register_shutdown_function("system_cleanup");
+}
+
+/**
+ *  db_query() - Query the database.
+ *
+ *  @param text SQL statement.
+ *  @param int How many rows do you want returned.
+ *  @param int Of matching rows, return only rows starting here.
+ *	@param int ability to spread load to multiple db servers.
+ *	@return int result set handle.
+ */
+function db_query($qstring,$limit='-1',$offset=0,$dbserver=SYS_DB_PRIMARY) {
+	global $QUERY_COUNT;
+	$QUERY_COUNT++;
+
+	if ($limit > 0) {
+		if (!$offset || $offset < 0) {
+			$offset=0;
+		}
+		$qstring=$qstring." LIMIT $limit OFFSET $offset";
+	}
+
+	//$GLOBALS['G_DEBUGQUERY'] .= $qstring .' |<font size="-2">'.$dbserver.'</font>'. "<p>\n";
+	$res = @pg_exec($dbserver,$qstring);
+	//echo "\n<br />|*| [$qstring]: ".db_error();
+	return $res;
+}
+
+/* Current transaction level, private variable */
+/* FIXME: Having scalar variable for transaction level is
+   no longer correct after multiple database (dbservers) support
+   introduction. However, it is true that in one given PHP
+   script, at most one db is modified, so this works for now. */
+$_sys_db_transaction_level = 0;
+
+/**
+ *	db_begin() - Begin a transaction.
+ *
+ *  @param		constant		Database server (SYS_DB_PRIMARY, SYS_DB_STATS, SYS_DB_TROVE, SYS_DB_SEARCH)
+ *	@return true.
+ */
+function db_begin($dbserver=SYS_DB_PRIMARY) {
+	global $_sys_db_transaction_level;
+
+	// start database transaction only for the top-level
+	// programmatical transaction
+	$_sys_db_transaction_level++;
+	if ($_sys_db_transaction_level == 1) {
+		return db_query("BEGIN WORK", -1, 0, $dbserver);
+	}
+
+	return true;
+}
+
+/**
+ *	db_commit() - Commit a transaction.
+ *
+ *  @param		constant		Database server (SYS_DB_PRIMARY, SYS_DB_STATS, SYS_DB_TROVE, SYS_DB_SEARCH)
+ *	@return true on success/false on failure.
+ */
+function db_commit($dbserver=SYS_DB_PRIMARY) {
+	global $_sys_db_transaction_level;
+
+	// check for transaction stack underflow
+	if ($_sys_db_transaction_level == 0) {
+		echo "COMMIT underflow<br />";
+		return false;
+	}
+
+	// commit database transaction only when top-level
+	// programmatical transaction ends
+	$_sys_db_transaction_level--;
+	if ($_sys_db_transaction_level == 0) {
+		return db_query("COMMIT", -1, 0, $dbserver);
+	}
+
+	return true;
+}
+
+/**
+ *	db_rollback() - Rollback a transaction.
+ *
+ *  @param		constant		Database server (SYS_DB_PRIMARY, SYS_DB_STATS, SYS_DB_TROVE, SYS_DB_SEARCH)
+ *	@return true on success/false on failure.
+ */
+function db_rollback($dbserver=SYS_DB_PRIMARY) {
+	global $_sys_db_transaction_level;
+
+	// check for transaction stack underflow
+	if ($_sys_db_transaction_level == 0) {
+		echo "ROLLBACK underflow<br />";
+		return false;
+	}
+
+	// rollback database transaction only when top-level
+	// programmatical transaction ends
+	$_sys_db_transaction_level--;
+	if ($_sys_db_transaction_level == 0) {
+		return db_query("ROLLBACK", -1, 0, $dbserver);
+	}
+
+	return true;
+}
+
+/**
+ *	db_numrows() - Returns the number of rows in this result set.
+ *
+ *	@param		int		Query result set handle.
+ *	@return int number of rows.
+ */
+
+function db_numrows($qhandle) {
+	return @pg_numrows($qhandle);
+}
+
+/**
+ *  db_free_result() - Frees a database result properly.
+ *
+ *	@param		int		Query result set handle.
+ */
+function db_free_result($qhandle) {
+	return @pg_freeresult($qhandle);
+}
+
+/**
+ *  db_reset_result() - Reset is useful for db_fetch_array
+ *  sometimes you need to start over.
+ *
+ *	@param		int		Query result set handle.
+ *  @param		integer	Row number.
+ *	@return int row.
+ */
+function db_reset_result($qhandle,$row=0) {
+	global $sys_db_row_pointer;
+	return $sys_db_row_pointer[$qhandle]=$row;
+}
+
+/**
+ *  db_result() - Returns a field from a result set.
+ *
+ *	@param		int		Query result set handle.
+ *  @param		integer Row number.
+ *  @param		string	Field name.
+ *	@return contents of field from database.
+ */
+function db_result($qhandle,$row,$field) {
+	return @pg_result($qhandle,$row,$field);
+}
+
+/**
+ *  db_numfields() - Returns the number of fields in this result set.
+ *
+ *	@param		int		Query result set handle.
+ */
+function db_numfields($lhandle) {
+	return @pg_numfields($lhandle);
+}
+
+/**
+ *  db_fieldname() - Returns the name of a particular field in the result set
+ *
+ *	@param		int		Query result set handle.
+ *  @param		int		Column number.
+ *	@return text name of the field.
+ */
+function db_fieldname($lhandle,$fnumber) {
+	return @pg_fieldname($lhandle,$fnumber);
+}
+
+/**
+ *  db_affected_rows() - Returns the number of rows changed in the last query.
+ *
+ *	@param		int		Query result set handle.
+ *	@return int number of affected rows.
+ */
+function db_affected_rows($qhandle) {
+	return @pg_cmdtuples($qhandle);
+}
+
+/**
+ *  db_fetch_array() - Returns an associative array from 
+ *  the current row of this database result
+ *  Use db_reset_result to seek a particular row.
+ *
+ *	@param		int		Query result set handle.
+ *	@return associative array of fieldname/value key pairs.
+ */
+function db_fetch_array($qhandle) {
+	global $sys_db_row_pointer;
+	if(!isset($sys_db_row_pointer[$qhandle])) {
+		$sys_db_row_pointer[$qhandle] = 0;
+	}
+	$sys_db_row_pointer[$qhandle]++;
+	return @pg_fetch_array($qhandle,($sys_db_row_pointer[$qhandle]-1));
+}
+
+/**
+ *  db_insertid() - Returns the last primary key from an insert.
+ *
+ *	@param		int		Query result set handle.
+ *  @param		string	table_name is the name of the table you inserted into.
+ *  @param		string	pkey_field_name is the field name of the primary key.
+ *  @param		string	Server to which original query was made
+ *	@return int id of the primary key or 0 on failure.
+ */
+function db_insertid($qhandle,$table_name,$pkey_field_name,$dbserver=SYS_DB_PRIMARY) {
+	$sql="SELECT max($pkey_field_name) AS id FROM $table_name";
+	//echo $sql;
+	$res=db_query($sql, -1, 0, $dbserver);
+	if (db_numrows($res) >0) {
+		return db_result($res,0,'id');
+	} else {
+	//	echo "No Rows Matched";
+	//	echo db_error();
+		return 0;
+	}
+}
+
+/**
+ *  db_error() - Returns the last error from the database.
+ *
+ *  @param		constant		Database server (SYS_DB_PRIMARY, SYS_DB_STATS, SYS_DB_TROVE, SYS_DB_SEARCH)
+ *	@return text error message.
+ */
+function db_error($dbserver=SYS_DB_PRIMARY) {
+	return @pg_errormessage($dbserver);
+}
+
+/**
+ *	system_cleanup() - In the future, we may wish to do a number 
+ *	of cleanup functions at script termination.
+ *
+ *	For now, we just abort any in-process transaction.
+ */
+function system_cleanup() {
+	global $_sys_db_transaction_level;
+	if ($_sys_db_transaction_level > 0) {
+		echo "Open transaction detected!!!";
+		db_query("ROLLBACK");
+	}
+}
+
+function db_drop_table_if_exists ($tn) {
+	$sql = "SELECT COUNT(*) FROM pg_class WHERE relname='$tn';";
+	$rel = db_query($sql);
+	echo db_error();
+	$count = db_result($rel,0,0);
+	if ($count != 0) {
+		$sql = "DROP TABLE $tn;";
+		$rel = db_query ($sql);
+		echo db_error();
+	}
+}
+
+function db_drop_sequence_if_exists ($tn) {
+	$sql = "SELECT COUNT(*) FROM pg_class WHERE relname='$tn';";
+	$rel = db_query($sql);
+	echo db_error();
+	$count = db_result($rel,0,0);
+	if ($count != 0) {
+		$sql = "DROP SEQUENCE $tn;";
+		$rel = db_query ($sql);
+		echo db_error();
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/include/escapingUtils.php
===================================================================
--- trunk/gforge_base/gforge/common/include/escapingUtils.php	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/escapingUtils.php	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,348 @@
+<?php
+
+/**
+ * GForge escaping library
+ *
+ * Copyright 2003-2004 Guillaume Smet
+ * http://gforge.org/
+ *
+ * @version   $Id: escapingUtils.php 4976 2005-11-16 14:06:18Z danper $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/**
+ * getIntFromRequest - get an int from REQUEST
+ *
+ * @param string $key key of the wanted value
+ * @param int $defaultValue if we can't find the wanted value, it returns the default value
+ * @return int the value
+ */
+function getIntFromRequest($key, $defaultValue = 0) {
+	return _getIntFromArray(_getRequestArray(), $key, $defaultValue);
+}
+
+/**
+ * getStringFromRequest - get a string from REQUEST
+ *
+ * @param string $key key of the wanted value
+ * @param string $defaultValue if we can't find the wanted value, it returns the default value
+ * @return string the value
+ */
+function getStringFromRequest($key, $defaultValue = '') {
+	return _getStringFromArray(_getRequestArray(), $key, $defaultValue);
+}
+
+/**
+ * getArrayFromRequest - get an array from REQUEST
+ * @param	string $key	Key of the wanted value
+ * @param	string $defaultValue	if we can't find the wanted value, it returns the default value
+ * @return	array	The value
+ */
+function getArrayFromRequest($key, $defaultValue = array()) {
+	return _getArrayFromArray(_getRequestArray(), $key, $defaultValue);
+}
+
+/**
+ * getIntFromPost - get an int from POST
+ *
+ * @param string $key key of the wanted value
+ * @param int $defaultValue if we can't find the wanted value, it returns the default value
+ * @return int the value
+ */
+function getIntFromPost($key, $defaultValue = 0) {
+	return _getIntFromArray(_getPostArray(), $key, $defaultValue);
+}
+
+/**
+ * getStringFromPost - get a string from POST
+ *
+ * @param string $key key of the wanted value
+ * @param string $defaultValue if we can't find the wanted value, it returns the default value
+ * @return string the value
+ */
+function getStringFromPost($key, $defaultValue = '') {
+	return _getStringFromArray(_getPostArray(), $key, $defaultValue);
+}
+
+/**
+ * getIntFromGet - get an int from GET
+ *
+ * @param string $key key of the wanted value
+ * @param int $defaultValue if we can't find the wanted value, it returns the default value
+ * @return int the value
+ */
+function getIntFromGet($key, $defaultValue = 0) {
+	return _getIntFromArray(_getGetArray(), $key, $defaultValue);
+}
+
+/**
+ * getStringFromGet - get a string from GET
+ *
+ * @param string $key key of the wanted value
+ * @param string $defaultValue if we can't find the wanted value, it returns the default value
+ * @return string the value
+ */
+function getStringFromGet($key, $defaultValue = '') {
+	return _getStringFromArray(_getGetArray(), $key, $defaultValue);
+}
+
+/**
+ * getIntFromCookie - get an int set by a cookie
+ *
+ * @param string $key key of the wanted value
+ * @param int $defaultValue if we can't find the wanted value, it returns the default value
+ * @return int the value
+ */
+function getIntFromCookie($key, $defaultValue = 0) {
+	return _getIntFromArray(_getCookieArray(), $key, $defaultValue);
+}
+
+/**
+ * getStringFromCookie - get a string set by a cookie
+ *
+ * @param string $key key of the wanted value
+ * @param string $defaultValue if we can't find the wanted value, it returns the default value
+ * @return string the value
+ */
+function getStringFromCookie($key, $defaultValue = '') {
+	return _getStringFromArray(_getCookieArray(), $key, $defaultValue);
+}
+
+/**
+ * getUploadedFile - get the uploaded file information
+ *
+ * @param string name of the file
+ * @return array uploaded file information
+ */
+function getUploadedFile($key) {
+	$filesArray = & _getFilesArray();
+	if(isset($filesArray[$key])) {
+		return $filesArray[$key];
+	}
+	else {
+		return array();
+	}
+}
+
+/**
+ * getStringFromServer - get a string from Server environment
+ *
+ * @param string $key key of the wanted value
+ * @param string $defaultValue if we can't find the wanted value, it returns the default value
+ * @return string the value
+ */
+function getStringFromServer($key) {
+	$serverArray = & _getServerArray();
+	if(isset($serverArray[$key])) {
+		return $serverArray[$key];
+	}
+	else {
+		return '';
+	}
+}
+
+/* private */
+
+/**
+ * _getIntFromArray - get an int from an array
+ *
+ * @param array $array the array
+ * @param string $key the key of the wanted value
+ * @param int $defaultValue an int which is returned if we can't find the key in the array
+ * @return int the wanted value
+ */
+function _getIntFromArray(& $array, $key, $defaultValue = 0) {
+	if(isset($array[$key]) && is_numeric($array[$key])) {
+		return (int) $array[$key];
+	}
+	elseif(is_numeric($defaultValue)) {
+		return (int) $defaultValue;
+	}
+	else {
+		return 0;
+	}
+}
+
+/**
+ * _getStringFromArray - get an int from an array
+ *
+ * @param array $array the array
+ * @param string $key the key of the wanted value
+ * @param int $defaultValue an int which is returned if we can't find the key in the array
+ * @return string the wanted value
+ */
+function _getStringFromArray(& $array, $key, $defaultValue = '') {
+	if(isset($array[$key])) {
+		return $array[$key];
+	}
+	else {
+		return $defaultValue;
+	}
+}
+
+/**
+ * _getArrayFromArray - get an array from another array
+ *
+ * @param array $array the array
+ * @param string $key the key of the wanted value
+ * @param int $defaultValue an array which is returned if we can't find the key in the array
+ * @return string the wanted value
+ */
+function _getArrayFromArray(& $array, $key, $defaultValue = array()) {
+	if(isset($array[$key])) {
+		return $array[$key];
+	}
+	else {
+		return $defaultValue;
+	}
+}
+
+/**
+ * _getPredefinedArray - get one of the predefined array (GET, POST, COOKIE...)
+ *
+ * @param string $superGlobalName name of the super global array (_POST, _GET)
+ * @param string $oldName name of the old array (HTTP_POST_VARS, HTTP_GET_VARS) for older php versions
+ * @return array a predefined array
+ */
+function & _getPredefinedArray($superGlobalName, $oldName) {
+	if(isset($GLOBALS[$superGlobalName])) {
+		$array = & $GLOBALS[$superGlobalName];
+	} elseif(isset($GLOBALS[$oldName])) {
+		$array = & $GLOBALS[$oldName];
+	} else {
+		$array = array();
+	}
+	return $array;
+}
+
+/**
+ * _getRequestArray - wrapper to get the request array
+ *
+ * @return array the REQUEST array
+ */
+function & _getRequestArray() {
+	if(isset($_REQUEST)) {
+		return $_REQUEST;
+	} else {
+		return array_merge($GLOBALS['HTTP_GET_VARS'], $GLOBALS['HTTP_POST_VARS'], $GLOBALS['HTTP_COOKIE_VARS']);
+	}
+}
+
+/**
+ * _getPostArray - wrapper to get the post array
+ *
+ * @return array the POST array
+ */
+function & _getPostArray() {
+	return _getPredefinedArray('_POST', 'HTTP_POST_VARS');
+}
+
+/**
+ * _getPostArray - wrapper to get the GET array
+ *
+ * @return array the GET array
+ */
+function & _getGetArray() {
+	return _getPredefinedArray('_GET', 'HTTP_GET_VARS');
+}
+
+/**
+ * _getFilesArray - wrapper to get the FILES array
+ *
+ * @return array the FILES array
+ */
+function & _getFilesArray() {
+	return _getPredefinedArray('_FILES', 'HTTP_POST_FILES');
+}
+
+/**
+ * _getServerArray - wrapper to get the SERVER array
+ *
+ * @return array the SERVER array
+ */
+function & _getServerArray() {
+	return _getPredefinedArray('_SERVER', 'HTTP_SERVER_VARS');
+}
+
+/**
+ * _getCookieArray - wrapper to get the post array
+ *
+ * @return array the COOKIE array
+ */
+function & _getCookieArray() {
+	return _getPredefinedArray('_COOKIE', 'HTTP_COOKIE_VARS');
+}
+
+/**
+* inputSpecialchars - escape a string which is in an input
+*
+* @param string $string string to escape
+* @return string escaped string
+*/
+function inputSpecialchars($string) {
+	return str_replace('"', '&quot;', $string);
+}
+	
+/**
+* unInputSpecialchars - clean a string escaped with inputSpecialchars
+*
+* @param string $string escaped string
+* @return string clean string
+*/
+function unInputSpecialchars($string) {
+	return str_replace('&quot;', '"', $string);
+}
+
+/**
+* optionSpecialchars - escape a string which is in a <option>string</option>
+*
+* @param string $string string to escape
+* @return string escaped string
+*/
+function optionSpecialchars($string) {
+	return htmlSpecialchars($string);
+}
+
+$htmlTranslationTable = get_html_translation_table(HTML_SPECIALCHARS);
+unset($htmlTranslationTable['&']);
+
+/**
+* textareaSpecialchars - escape a string which is in a textarea
+*
+* @param string $string string to escape
+* @return string escaped string
+*/
+function textareaSpecialchars($string) {
+	global $htmlTranslationTable;
+
+	return strtr($string, $htmlTranslationTable);
+}
+
+/**
+* unTextareaSpecialchars - clean a string escaped with textareaSpecialchars
+*
+* @param string $string escaped string
+* @return string clean string
+*/
+function unTextareaSpecialchars($string) {
+	global $htmlTranslationTable;
+	
+	return strtr($string, array_flip($htmlTranslationTable));
+}
+
+?>

Added: trunk/gforge_base/gforge/common/include/license.php
===================================================================
--- trunk/gforge_base/gforge/common/include/license.php	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/license.php	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,86 @@
+<?php
+/**
+ * Licensing - licenses have been moved into tables.
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: license.php 3131 2004-07-22 20:10:18Z tperdue $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2004-07-22
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+function license_getname($id) {
+	global $license_arr;
+	if (!$license_arr[$id]) {
+		$res=db_query("SELECT * FROM licenses WHERE license_id='$id'");
+		$license_arr[$id]=db_result($res,0,'license_name');
+	}
+	return $license_arr[$id];
+}
+
+function license_add($name) {
+	global $feedback;
+	$res=db_query("INSERT INTO licenses(license_name) 
+		values ('".htmlspecialchars($name)."')");
+	if (!$res) {
+		$feedback .= ' Error adding License: '.db_error();
+		return false;
+	} else {
+		return true;
+	}
+}
+
+function license_update($id,$name) {
+	global $feedback;
+	$res=db_query("UPDATE licenses 
+		SET license_name='".htmlspecialchars($name)."'
+		WHERE license_id='$id'");
+	if (!$res) {
+		$feedback .= ' Error adding License: '.db_error();
+		return false;
+	} else {
+		return true;
+	}
+}
+
+function license_delete($id) {
+	global $feedback;
+	$res=db_query("UPDATE groups
+		SET license_id='100'
+		WHERE license_id='$id'");
+	if (!$res) {
+		$feedback .= ' Error deleting License: '.db_error();
+		return false;
+	} else {
+		$res=db_query("DELETE FROM licenses WHERE license_id='$id'");
+		if (!$res) {
+			$feedback .= ' Error deleting License: '.db_error();
+			return false;
+		} else {
+			return true;
+		}
+	}
+}
+
+function license_selectbox($title='license_id',$selected='xzxz') {
+    $res=db_query("SELECT license_id, license_name FROM licenses");
+    return html_build_select_box($res,$title,$selected,false);
+}
+
+?>

Added: trunk/gforge_base/gforge/common/include/scm.php
===================================================================
--- trunk/gforge_base/gforge/common/include/scm.php	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/scm.php	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,10 @@
+<?php
+
+$scm_list = array () ;
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/include/session.php
===================================================================
--- trunk/gforge_base/gforge/common/include/session.php	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/session.php	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,522 @@
+<?php
+/**
+ * SourceForge Session Module
+ *
+ * Copyright 1999-2001 (c) VA Linux Systems
+ *
+ * @version   $Id: session.php 5588 2006-06-28 13:30:08Z federicot $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/include/account.php');
+
+/**
+ * A User object if user is logged in
+ *
+ * @var	constant		$G_SESSION
+ */
+$G_SESSION = false;
+
+/**
+ *	session_build_session_cookie() - Construct session cookie for the user
+ *
+ *	@param		int		User_id of the logged in user
+ *	@return cookie value
+ */
+function session_build_session_cookie($user_id) {
+	$session_serial = $user_id.'-*-'.time().'-*-'.$GLOBALS['REMOTE_ADDR'].'-*-'.$GLOBALS['HTTP_USER_AGENT'];
+	$session_serial_hash = md5($session_serial.$GLOBALS['sys_session_key']);
+	$session_serial_cookie = base64_encode($session_serial).'-*-'.$session_serial_hash;
+	return $session_serial_cookie;
+}
+
+/**
+ *	session_get_session_cookie_hash() - Get hash of session cookie
+ *
+ *	This hash can be used as a key to identify session, e.g. in DB.
+ *
+ *	@param		string	Value of the session cookie
+ *	@return hash
+ */
+function session_get_session_cookie_hash($session_cookie) {
+	list ($junk, $hash) = explode('-*-', $session_cookie);
+	return $hash;
+}
+
+/**
+ *	session_check_session_cookie() - Check that session cookie passed from user is ok
+ *
+ *	@param		string	Value of the session cookie
+ *	@return user_id if cookie is ok, false otherwise
+ */
+function session_check_session_cookie($session_cookie) {
+
+	list ($session_serial, $hash) = explode('-*-', $session_cookie);
+	$session_serial = base64_decode($session_serial);
+	$new_hash = md5($session_serial.$GLOBALS['sys_session_key']);
+
+	if ($hash != $new_hash) {
+		return false;
+	}
+
+	list($user_id, $time, $ip, $user_agent) = explode('-*-', $session_serial, 4);
+
+	if (!session_check_ip($ip, $GLOBALS['REMOTE_ADDR'])) {
+		return false;
+	}
+	if (trim($user_agent) != $GLOBALS['HTTP_USER_AGENT']) {
+		return false;
+	}
+	if ($time - time() >= $GLOBALS['sys_session_expire']) {
+		return false;
+	}
+
+	return $user_id;
+}
+
+/**
+ *	session_logout() - Log the user off the system.
+ *
+ *	This function destroys object associated with the current session,
+ *	making user "logged out".  Deletes both user and session cookies.
+ *
+ *	@return true/false
+ *
+ */
+function session_logout() {
+
+	// delete both session and username cookies
+	// NB: cookies must be deleted with the same scope parameters they were set with
+	//
+	session_cookie('session_ser', '');
+	return true;
+}
+
+/**
+ *	session_login_valid() - Log the user to the system.
+ *
+ *	High-level function for user login. Check credentials, and if they
+ *	are valid, open new session.
+ *
+ *	@param		string	User name
+ *	@param		string	User password (in clear text)
+ *	@param		bool	Allow login to non-confirmed user account (only for confirmation of the very account)
+ *	@return true/false, if false reason is in global $feedback
+ *	@access public
+ *
+ */
+function session_login_valid($loginname, $passwd, $allowpending=0)  {
+	global $feedback,$Language;
+
+	if (!$loginname || !$passwd) {
+		$feedback = $Language->getText('session','missingpasswd');
+		return false;
+	}
+
+	$hook_params = array () ;
+	$hook_params['loginname'] = $loginname ;
+	$hook_params['passwd'] = $passwd ;
+	plugin_hook ("session_before_login", $hook_params) ;
+
+	return session_login_valid_dbonly ($loginname, $passwd, $allowpending) ;
+}
+
+function session_login_valid_dbonly ($loginname, $passwd, $allowpending) {
+	global $feedback,$userstatus,$Language;
+
+	//  Try to get the users from the database using user_id and (MD5) user_pw
+	$res = db_query("
+		SELECT user_id,status,unix_pw
+		FROM users
+		WHERE user_name='$loginname' 
+		AND user_pw='".md5($passwd)."'
+	");
+	if (!$res || db_numrows($res) < 1) {
+		// No user whose MD5 passwd matches the MD5 of the provided passwd
+		// Selecting by user_name only
+		$res = db_query("SELECT user_id,status,unix_pw
+					FROM users
+					WHERE user_name='$loginname'");
+		if (!$res || db_numrows($res) < 1) {
+			// No user by that name
+			$feedback=$Language->getText('session','invalidpasswd');
+			return false;
+		} else {
+			// There is a user with the provided user_name, but the MD5 passwds do not match
+			// We'll have to try checking the (crypt) unix_pw
+			$usr = db_fetch_array($res);
+
+			if (crypt ($passwd, $usr['unix_pw']) != $usr['unix_pw']) {
+				// Even the (crypt) unix_pw does not patch
+				// This one has clearly typed a bad passwd
+				$feedback=$Language->getText('session','invalidpasswd');
+				return false;
+			}
+			// User exists, (crypt) unix_pw matches
+			// Update the (MD5) user_pw and retry authentication
+			// It should work, except for status errors
+			$res = db_query ("UPDATE users
+				SET user_pw='" . md5($passwd) . "'
+				WHERE user_id='".$usr['user_id']."'");
+			return session_login_valid_dbonly($loginname, $passwd, $allowpending) ;
+		}
+	} else {
+		// If we're here, then the user has typed a password matching the (MD5) user_pw
+		// Let's check whether it also matches the (crypt) unix_pw
+		$usr = db_fetch_array($res);
+
+		if (crypt ($passwd, $usr['unix_pw']) != $usr['unix_pw']) {
+			// The (crypt) unix_pw does not match
+			if ($usr['unix_pw'] == '') {
+				// Empty unix_pw, we'll take the MD5 as authoritative
+				// Update the (crypt) unix_pw and retry authentication
+				// It should work, except for status errors
+				$res = db_query ("UPDATE users
+					SET unix_pw='" . account_genunixpw($passwd) . "'
+					WHERE user_id='".$usr['user_id']."'");
+				return session_login_valid_dbonly($loginname, $passwd, $allowpending) ;
+			} else {
+				// Invalidate (MD5) user_pw, refuse authentication
+				$res = db_query ("UPDATE users
+					SET user_pw='OUT OF DATE'
+					WHERE user_id='".$usr['user_id']."'");
+				$feedback=$Language->getText('session','invalidpasswd');
+				return false;
+			}
+		}
+
+		// Yay.  The provided password matches both fields in the database.
+		// Let's check the status of this user
+
+		// if allowpending (for verify.php) then allow
+		$userstatus=$usr['status'];
+		if ($allowpending && ($usr['status'] == 'P')) {
+			//1;
+		} else {
+			if ($usr['status'] == 'S') { 
+				//acount suspended
+				$feedback = $Language->getText('session','suspended');
+				return false;
+			}
+			if ($usr['status'] == 'P') { 
+				//account pending
+				$feedback = $Language->getText('session','pending');
+				return false;
+			} 
+			if ($usr['status'] == 'D') { 
+				//account deleted
+				$feedback = $Language->getText('session','deleted');
+				return false;
+			}
+			if ($usr['status'] != 'A') {
+				//unacceptable account flag
+				$feedback = $Language->getText('session','notactive');
+				return false;
+			}
+		}
+		//create a new session
+		session_set_new(db_result($res,0,'user_id'));
+
+		return true;
+	}
+}
+
+/**
+ *	session_check_ip() - Check 2 IP addresses for match
+ *
+ *	This function checks that IP addresses match with the
+ *	given fuzz factor (within 255.255.0.0 subnet).
+ *
+ *	@param		string	The old IP address
+ *	@param		string	The new IP address
+ *	@return true/false
+ *	@access private
+ */
+function session_check_ip($oldip,$newip) {
+	$eoldip = explode(".",$oldip);
+	$enewip = explode(".",$newip);
+
+	// ## require same class b subnet
+	if (($eoldip[0]!=$enewip[0])||($eoldip[1]!=$enewip[1])) {
+		return 0;
+	} else {
+		return 1;
+	}
+}
+
+/**
+ *	session_issecure() - Check if current session is secure
+ *
+ *	@return true/false
+ *	@access public
+ */
+function session_issecure() {
+	global $HTTP_SERVER_VARS;
+	return (strtoupper($HTTP_SERVER_VARS['HTTPS']) == "ON");
+}
+
+/**
+ *	session_cookie() - Set a session cookie
+ *
+ *	Set a cookie with default temporal scope of the current browser session
+ *	and URL space of the current webserver
+ *
+ *	@param		string	Name of cookie
+ *	@param		string	Value of cookie
+ *	@param		string	Domain scope (default '')
+ *	@param		string	Expiration time in UNIX seconds (default 0)
+ *	@return true/false
+ */
+function session_cookie($name ,$value, $domain = '', $expiration = 0) {
+	if ( $expiration != 0){
+		setcookie($name, $value, time() + $expiration, '/', $domain, 0);
+	} else {
+		setcookie($name, $value, $expiration, '/', $domain, 0);
+	}
+}
+
+/**
+ *	session_redirect() - Redirect browser within the site
+ *
+ *	@param		string	Absolute path within the site
+ *	@return never returns
+ */
+function session_redirect($loc) {
+	header('Location: http' . (session_issecure()?'s':'') . '://' . getStringFromServer('HTTP_HOST') . $loc);
+	print("\n\n");
+	exit;
+}
+
+/**
+ *	session_require() - Convenience function to easily enforce permissions
+ *
+ *	Calling page will terminate with error message if current user
+ *	fails checks.
+ *
+ *	@param		array	Associative array specifying criteria
+ *	@param		string	Override error string (optional)
+ *	@return does not return if check is failed
+ *
+ */
+function session_require($req,$xreason='') {
+	if (!session_loggedin()) {
+		exit_not_logged_in();	
+	}
+
+	if ($req['group']) {
+		$group =& group_get_object($req['group']);
+		if (!$group || !is_object($group)) {
+			exit_error('Error',$xreason == '' ? 'Could Not Get Group' : $xreason);
+		} elseif ($group->isError()) {
+			exit_error('Error',$xreason == '' ? $group->getErrorMessage() : $xreason);
+		}
+
+		$perm =& $group->getPermission( session_get_user() );
+		if (!$perm || !is_object($perm) || $perm->isError()) {
+			exit_permission_denied($xreason);
+		}
+
+		if ($req['admin_flags']) {
+			if (!$perm->isAdmin()) {
+				exit_permission_denied($xreason);
+			}
+		} else {
+			if (!$perm->isMember()) {
+				exit_permission_denied($xreason);
+			}
+		}
+	} else if ($req['isloggedin']) {
+		//no need to check as long as the check is present at top of function
+	} else {
+		exit_permission_denied($xreason);
+	}
+}
+
+/**
+ *	session_set_new() - Setup session for the given user
+ *
+ *	This function sets up SourceForge session for the given user,
+ *	making one be "logged in".
+ *
+ *	@param		int		The user ID
+ *	@return none
+ */
+function session_set_new($user_id) {
+	global $G_SESSION,$session_ser,$Language;
+
+	// set session cookie
+  //
+	$cookie = session_build_session_cookie($user_id);
+	session_cookie("session_ser", $cookie, "", $GLOBALS['sys_session_expire']);
+	$session_ser=$cookie;
+
+	db_query("
+		INSERT INTO user_session (session_hash, ip_addr, time, user_id) 
+		VALUES (
+			'".session_get_session_cookie_hash($cookie)."', 
+			'".$GLOBALS['REMOTE_ADDR']."',
+			'".time()."',
+			$user_id
+		)
+	");
+
+	// check uniqueness of the session_hash in the database
+	// 
+	$res = session_getdata($user_id);
+
+	if (!$res || db_numrows($res) < 1) {
+		exit_error($Language->getText('global','error'),$Language->getText('session','cannotinit').": ".db_error());
+	} else {
+
+		//set up the new user object
+		//
+		$G_SESSION = user_get_object($user_id,$res);
+		if ($G_SESSION) {
+			$G_SESSION->setLoggedIn(true);
+		}
+	}
+
+}
+
+/**
+ *	Private optimization function for logins - fetches user data, language, and session
+ *	with one query
+ *
+ *  @param		int		The user ID
+ *	@access private
+ */
+function session_getdata($user_id) {
+	$res=db_query("SELECT
+		u.*,sl.language_id, sl.name, sl.filename, sl.classname, sl.language_code, t.dirname, t.fullname
+		FROM users u,
+		supported_languages sl,
+		themes t
+		WHERE u.language=sl.language_id 
+		AND u.theme_id=t.theme_id
+		AND u.user_id='$user_id'");
+	return $res;
+}
+
+/**
+ *	session_set() - Re-initialize session for the logged in user
+ *
+ *	This function checks that the user is logged in and if so, initialize
+ *	internal session environment.
+ *
+ *	@return none
+ */
+function session_set() {
+	global $G_SESSION;
+	global $session_ser, $session_key;
+
+	// assume bad session_hash and session. If all checks work, then allow
+	// otherwise make new session
+	$id_is_good = false;
+
+	// If user says he's logged in (by presenting cookie), check that
+	if ($session_ser) {
+
+		$user_id = session_check_session_cookie($session_ser);
+
+		if ($user_id) {
+
+			$result = session_getdata($user_id);
+
+			if (db_numrows($result) > 0) {
+				$id_is_good = true;
+			}
+		}
+	} // else (hash does not exist) or (session hash is bad)
+
+	if ($id_is_good) {
+		$G_SESSION = user_get_object($user_id, $result);
+		if ($G_SESSION) {
+			$G_SESSION->setLoggedIn(true);
+		}
+	} else {
+		$G_SESSION=false;
+
+		// if there was bad session cookie, kill it and the user cookie
+		//
+		if ($session_ser) {
+			session_logout();
+		}
+	}
+}
+
+//TODO - this should be generalized and used for pre.php, squal_pre.php, 
+//SOAP, forum_gateway.php, tracker_gateway.php, etc to 
+//setup languages
+function session_continue($sessionKey) {
+	global $session_ser, $Language, $sys_strftimefmt, $sys_datefmt;
+	$session_ser = $sessionKey;
+	session_set();
+ 	$Language=new BaseLanguage();
+	$Language->loadLanguage("English"); // TODO use the user's default language
+	setlocale (LC_TIME, $Language->getText('system','locale'));
+	$sys_strftimefmt = $Language->getText('system','strftimefmt');
+	$sys_datefmt = $Language->getText('system','datefmt');
+	$LUSER =& session_get_user();
+	if (!is_object($LUSER) || $LUSER->isError()) {
+		return false;
+	} else {
+		putenv('TZ='. $LUSER->getTimeZone());
+		return true;
+	}
+}
+
+/**
+ *	session_get_user() - Wrapper function to return the User object for the logged in user.
+ *	
+ *	@return User
+ *	@access public
+ */
+function &session_get_user() {
+	global $G_SESSION;
+	return $G_SESSION;
+}
+
+/**
+ *  user_getid()
+ *  Get user_id of logged in user
+ */
+
+function user_getid() {
+	global $G_SESSION;
+	if ($G_SESSION) {
+		return $G_SESSION->getID();
+	} else {
+		return false;
+	}
+}
+
+/**
+ *  session_loggedin()
+ *  See if user is logged in
+ */
+function session_loggedin() {
+	global $G_SESSION;
+
+	if ($G_SESSION) {
+		return $G_SESSION->isLoggedIn();
+	} else {
+		return false;
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/include/system/LDAP.class
===================================================================
--- trunk/gforge_base/gforge/common/include/system/LDAP.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/system/LDAP.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,712 @@
+<?php
+/**
+ * LDAP class
+ *
+ * Class to interact with the system
+ *
+ * @version   $Id: LDAP.class 3978 2005-02-26 15:15:49Z cbayle $
+ * @author Christian Bayle
+ * @date 2004-02-05
+ *
+ * This file is part of GForge.
+ * It's OO version of ancient ldap.php
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+require_once('common/include/account.php');
+require_once('common/include/system/UNIX.class');
+
+class LDAP extends UNIX {
+	/**
+	*	LDAP()
+	*
+	*/
+	function LDAP() {
+		$this->UNIX();
+		return true;
+	}
+
+	/*
+ 	* Auxilary functions
+ 	*/
+	
+	/**
+ 	*	asciize() - Replace non-ascii characters with question marks
+ 	*
+ 	*	LDAP expects utf-8 encoded character string. Since we cannot
+ 	*	know which encoding 8-bit characters in database use, we
+ 	*	just replace them with question marks.
+ 	*
+ 	*  @param		string	UTF-8 encoded character string.
+ 	*	@return string which contains only ascii characters
+ 	*/
+	function asciize($str) {
+		if (!$str) {
+			// LDAP don't allow empty strings for some attributes
+			return '?';
+		}
+	
+		return ereg_replace("[\x80-\xff]","?",$str);
+	}
+
+	/*
+	 * Wrappers for PHP LDAP functions
+	 */
+
+	/**
+	 * gfLdapConnect() - Connect to the LDAP server
+	 *
+	 * @returns true on success/false on error
+	 *
+	 */
+	function gfLdapConnect() {
+		global $sys_ldap_host,$sys_ldap_port;
+		global $sys_ldap_bind_dn,$sys_ldap_passwd,$ldap_conn,$sys_ldap_version;
+
+		if (!$ldap_conn) {
+			$this->clearError();
+			$ldap_conn = @ldap_connect($sys_ldap_host,$sys_ldap_port);
+			if (!$ldap_conn) {
+				$this->setError('ERROR: Cannot connect to LDAP server<br />');
+				return false;
+			}
+			if ($sys_ldap_version) {
+				ldap_set_option($ldap_conn, LDAP_OPT_PROTOCOL_VERSION, $sys_ldap_version);
+			}
+			ldap_bind($ldap_conn,$sys_ldap_bind_dn,$sys_ldap_passwd);
+		}
+		return true;
+	}
+
+	/**
+	 * gfLdapAdd() - Wrapper for ldap_add()
+	 * 
+	 * @param		string	dn
+	 * @param		string	entry
+	 *
+	 */
+	function gfLdapAdd($dn, $entry) {
+		global $ldap_conn;
+		return @ldap_add($ldap_conn,$dn,$entry);
+	}
+
+	/**
+	 * gfLdapDelete() - Wrapper for ldap_delete()
+	 *
+	 * @param		string	dn
+	 *
+	 */
+	function gfLdapDelete($dn) {
+		global $ldap_conn;
+		return @ldap_delete($ldap_conn,$dn);
+	}
+
+	/**
+	 * gfLdapModify() - Wrapper for ldap_modify()
+	 *
+	 * @param		string	dn
+	 * @param		string	entry
+	 *
+	 */
+	function gfLdapModify($dn,$entry) {
+		global $ldap_conn;
+		return @ldap_modify($ldap_conn,$dn,$entry);
+	}
+	
+	/**
+	 * gfLdapModifyIfExists() - Wrapper for ldap_modify()
+	 * works like gfLdapModify, but returns true if the LDAP entry does not exist
+	 *
+	 * @param		string	dn
+	 * @param		string	entry
+	 *
+	 */
+	function gfLdapModifyIfExists($dn,$entry) {
+        	$res = $this->gfLdapModify($dn,$entry);
+        	if ($res) {
+                	return true ;
+        	} else {
+                	$err = ldap_errno ($ldap_conn) ;
+                	if ($err == 32) {
+                        	return true ;
+                	} else {
+                        	return false ;
+                	}
+        	};
+	}
+
+	/**
+	 * gfLdapModAdd() - Wrapper for ldap_mod_add()
+	 *
+	 * @param		string	dn
+	 * @param		string	entry
+	 *
+	 */
+	function gfLdapModAdd($dn,$entry) {
+		global $ldap_conn;
+		return @ldap_mod_add($ldap_conn,$dn,$entry);
+	}
+	
+	/**
+	 * gfLdapModDel() - Wrapper for ldap_mod_del()
+	 *
+	 * @param		string	dn
+	 * @param		string	entry
+	 *
+	 */
+	function gfLdapModDel($dn,$entry) {
+		global $ldap_conn;
+		return @ldap_mod_del($ldap_conn,$dn,$entry);
+	}
+	
+	/**
+	 * gfLdapRead() - Wrapper for ldap_read()
+	 *
+	 * @param		string	dn
+	 * @param		string	filter
+	 * @param		int		attrs
+	 *
+	 */
+	function gfLdapRead($dn,$filter,$attrs=0) {
+		global $ldap_conn;
+		return @ldap_read($ldap_conn,$dn,$filter,$attrs);
+	}
+	
+	/**
+	 * gfLdapError() - Wrapper for ldap_error()
+	 *
+	 * @see ldap_error()
+	 *
+	 */
+	function gfLdapError() {
+		global $ldap_conn;
+		return ldap_error($ldap_conn);
+	}
+	
+	/**
+	 * gfLdapErrno() - Wrapper for ldap_errno()
+	 *
+	 * @see ldap_errno()
+	 *
+	 */
+	function gfLdapErrno() {
+		global $ldap_conn;
+		return ldap_errno($ldap_conn);
+	}
+	
+	/**
+	 * gfLdapAlreadyExists()
+	 */
+	function gfLdapAlreadyExists() {
+		global $ldap_conn;
+		return ldap_errno($ldap_conn)==20;
+	}
+	
+	/**
+	 * gfLdapDoesNotExist()
+	 */
+	function gfLdapDoesNotExist() {
+		global $ldap_conn;
+		return ldap_errno($ldap_conn)==16;
+	}
+	
+	/*
+	 * User management functions
+	 */
+	
+	/**
+	 * sysCheckUser() - Check for the existence of a user
+	 * 
+	 * @param		int		The user ID of the user to check
+	 * @returns true on success/false on error
+	 *
+	 */
+	function sysCheckUser($user_id) {
+		$user =& user_get_object($user_id);
+		if (!$user) {
+			return false;
+		}
+		return $this->gfLdapcheck_user_by_name($user->getUnixName());
+	}
+	
+	/**
+	 * gfLdapcheck_user_by_name() - Check for a user by the username
+	 *
+	 * @param		string	The username 
+	 * @returns true on success/false on error
+	 *
+	 */
+	function gfLdapcheck_user_by_name($user_name) {
+		global $ldap_conn;
+		global $sys_ldap_base_dn;
+	
+		if (!$this->gfLdapConnect()) {
+			return false;
+		}
+	
+		$dn = 'uid='.$user_name.',ou=People,'.$sys_ldap_base_dn;
+		$res = $this->gfLdapRead($dn,"objectClass=*",array("uid"));
+		if ($res) {
+			ldap_free_result($res);
+			return true;
+		}
+	
+		return false;
+	}
+	
+	/**
+	 * sysCreateUser() - Create a user
+	 *
+	 * @param		int	The user ID of the user to create
+	 * @returns The return status of gfLdapcreate_user_from_object()
+	 *
+	 */
+	function sysCreateUser($user_id) {
+		// Check even if the user shouldn't exist
+		// It can be created by a cron
+		if (!$this->sysCheckUser($user_id)){
+			$user = &user_get_object($user_id);
+			return $this->gfLdapcreate_user_from_object($user);
+		}
+		return true;
+	}
+	
+	/**
+	 * sysCheckCreateUser() - Check that a user has been created
+	 *
+	 * @param		int		The ID of the user to check
+	 * @returns true on success/false on error
+	 *
+	 */
+	function sysCheckCreateUser($user_id) {
+		if (!$this->sysCheckUser($user_id)){
+			$user = &user_get_object($user_id);
+			return $this->gfLdapcreate_user_from_object($user);
+		}
+		return true;
+	}
+	
+	/**
+	 * gfLdapcreate_user_from_object() - Create a user from information contained within an object
+	 *
+	 * @param		object	The user object
+	 * @returns true on success/false on error
+	 *
+	 */
+	function gfLdapcreate_user_from_object(&$user) {
+		global $sys_ldap_base_dn;
+		if (!$this->gfLdapConnect()) {
+			return false;
+		}
+		$dn = 'uid='.$user->getUnixName().',ou=People,'.$sys_ldap_base_dn;
+		$entry['objectClass'][0]='top';
+		$entry['objectClass'][1]='account';
+		$entry['objectClass'][2]='posixAccount';
+		$entry['objectClass'][3]='shadowAccount';
+		$entry['objectClass'][4]='debGforgeAccount';
+		$entry['uid']=$user->getUnixName();
+		$entry['cn']=$this->asciize($user->getRealName());
+		$entry['gecos']=$this->asciize($user->getRealName());
+		$entry['userPassword']='{crypt}'.$user->getUnixPasswd();
+		$entry['homeDirectory'] = account_user_homedir($user->getUnixName());
+		$entry['loginShell']=$user->getShell();
+		$entry['debGforgeCvsShell']="/bin/cvssh"; // unless explicitly set otherwise, developer has write access
+		$entry['debGforgeForwardEmail']=$user->getEmail();
+		$entry['uidNumber']=$this->getUnixUID();
+		$entry['gidNumber']=$this->getUnixGID(); // users as in debian backend
+		$entry['shadowLastChange']=1; // We don't have expiration, so any non-0
+		$entry['shadowMax']=99999;
+		$entry['shadowWarning']=7;
+	
+		if (!$this->gfLdapAdd($dn,$entry)) {
+			$this->setError("ERROR: cannot add LDAP user entry '".
+				 $user->getUnixName()."': ".$this->gfLdapError()."<br />");
+			return false;
+		}
+		return true;
+	}
+	
+	/**
+	 * gfLdapCreateUserFromProps() - Creates an LDAP user from
+	 *
+	 * @param		string	The username 
+	 * @param		string	????
+	 * @param		string	The encrypted password
+	 * @returns true on success/false on error
+	 *
+	 */
+	function gfLdapCreateUserFromProps($username, $cn, $crypt_pw,
+						$shell, $cvsshell, $uid, $gid, $email) {
+		global $sys_ldap_base_dn;
+		if (!$this->gfLdapConnect()) {
+			return false;
+		}
+		$dn = 'uid='.$username.',ou=People,'.$sys_ldap_base_dn;
+		$entry['objectClass'][0]='top';
+		$entry['objectClass'][1]='account';
+		$entry['objectClass'][2]='posixAccount';
+		$entry['objectClass'][3]='shadowAccount';
+		$entry['objectClass'][4]='debGforgeAccount';
+		$entry['uid']=$username;
+		$entry['cn']=$this->asciize($cn);
+		$entry['gecos']=$this->asciize($cn);
+		$entry['userPassword']='{crypt}'.$crypt_pw;
+		$entry['homeDirectory'] = account_user_homedir($username);
+		$entry['loginShell']=$shell;
+		$entry['debGforgeCvsShell']=$cvsshell; 
+		$entry['debGforgeForwardEmail']=$email;
+		$entry['uidNumber']=$uid;
+		$entry['gidNumber']=$gid;
+		$entry['shadowLastChange']=1;
+		$entry['shadowMax']=99999;
+		$entry['shadowWarning']=7;
+	
+		if (!$this->gfLdapAdd($dn,$entry)) {
+			$this->setError("ERROR: cannot add LDAP user entry '".
+				 $username."': ".$this->gfLdapError()."<br />");
+			return false;
+		}
+		return true;
+	}
+	
+	/**
+	 * sysRemoveUser() - Remove an LDAP user
+	 *
+	 * @param		int		The user ID of the user to remove
+	 * @returns true on success/false on failure
+	 *
+	 */
+	function sysRemoveUser($user_id) {
+		global $sys_ldap_base_dn;
+	
+		$user = &user_get_object($user_id);
+		if (!$this->gfLdapConnect()) {
+			return false;
+		}
+		$dn = 'uid='.$user->getUnixName().',ou=People,'.$sys_ldap_base_dn;
+	
+		if (!$this->gfLdapDelete($dn)) {
+		    $this->setError("ERROR: cannot delete LDAP user entry '".
+				 $user->getUnixName()."': ".$this->gfLdapError()."<br />");
+		    return false;
+		}
+		return true;
+	}
+	
+	/**
+	 * sysUserSetAttribute() - Set an attribute for a user
+	 *
+	 * @param		int		The user ID 
+	 * @param		string	The attribute to set
+	 * @param		string	The new value of the attribute
+	 * @returns true on success/false on error
+	 *
+	 */
+	function sysUserSetAttribute($user_id,$attr,$value) {
+		global $sys_ldap_base_dn;
+	
+		$user = &user_get_object($user_id);
+		if (!$this->gfLdapConnect()) {
+			return false;
+		}
+		$dn = 'uid='.$user->getUnixName().',ou=People,'.$sys_ldap_base_dn;
+		$entry[$attr]=$value;
+	
+		if (!$this->gfLdapModifyIfExists($dn, $entry)) {
+		    $this->setError("ERROR: cannot change LDAP attribute '$attr' for user '".
+				 $user->getUnixName()."': ".$this->gfLdapError()."<br />");
+		    return false;
+		}
+		return true;
+	}
+	
+	/*
+	 * Group management functions
+	 */
+	
+	/**
+	 * sysCheckGroup() - Check for the existence of a group
+	 * 
+	 * @param		int		The ID of the group to check
+	 * @returns true on success/false on error
+	 *
+	 */
+	function sysCheckGroup($group_id) {
+		global $ldap_conn;
+		global $sys_ldap_base_dn;
+	
+		$group = &group_get_object($group_id);
+		if (!$group) {
+			$this->setError("ERROR: Cannot find group [$group_id]<br />");
+			return false;
+		}
+		if (!$this->gfLdapConnect()) {
+			return false;
+		}
+		$dn = 'cn='.$group->getUnixName().',ou=Group,'.$sys_ldap_base_dn;
+		$res=$this->gfLdapRead($dn, "objectClass=*", array("cn"));
+		if ($res) {
+			ldap_free_result($res);
+			return true;
+		}
+		return false;
+	}
+	
+	/**
+	 * sysCreateGroup() - Create a group
+	 * 
+	 * @param		int		The ID of the group to create
+	 * @returns true on success/false on error
+	 *
+	 */
+	function sysCreateGroup($group_id) {
+		global $sys_ldap_base_dn;
+	
+		$group = &group_get_object($group_id);
+		if (!$this->gfLdapConnect()) {
+			return false;
+		}
+		$dn = 'cn='.$group->getUnixName().',ou=Group,'.$sys_ldap_base_dn;
+		$entry['objectClass'][0]='top';
+		$entry['objectClass'][1]='posixGroup';
+		$entry['cn']=$group->getUnixName();
+		$entry['userPassword']='{crypt}x';
+		$entry['gidNumber']=$this->getUnixGID();
+	
+		$i=0; $i_cvs=0;
+	
+		$ret_val=true;
+		
+		if (!$this->gfLdapAdd($dn,$entry)) {
+		    $this->setError("ERROR: cannot add LDAP group entry '".
+				 $group->getUnixName()."': ".$this->gfLdapError()."<br />");
+		    // If there's error, that's bad. But don't stop.
+		    $ret_val=false;
+		}
+	
+		//
+		//	Now create CVS group
+		//
+	
+		// Add virtual anoncvs user to CVS group
+		$cvs_member_list[$i_cvs++] = 'anoncvs_'.$group->getUnixName();
+	
+		$dn = 'cn='.$group->getUnixName().',ou=cvsGroup,'.$sys_ldap_base_dn;
+	
+		if ($cvs_member_list) {
+			$entry['memberUid']=$cvs_member_list;
+		} else {
+			unset($entry['memberUid']);
+		}
+	
+		if (!$this->gfLdapAdd($dn,$entry)) {
+			$this->setError("ERROR: cannot add LDAP CVS group entry '"
+				 .$group->getUnixName()."': ".$this->gfLdapError()."<br />");
+			$ret_val=false;
+		}
+	
+		//
+		// Finally, setup AnonCVS virtual user
+		//
+	
+	        if (!$this->gfLdapcheck_user_by_name('anoncvs_'.$group->getUnixName())
+		    && !$this->gfLdapCreateUserFromProps('scm_'.$group->getUnixName(),
+							'anoncvs', 'x',
+							'/bin/false', '/bin/false',
+							$this->getSCMGID(),
+							$this->getUnixGID(), "/dev/null")) {
+			$this->setError("ERROR: cannot add LDAP AnonCVS user entry '"
+				 .$group->getUnixName()."': ".$this->gfLdapError()."<br />");
+			$ret_val=false;
+		}
+	
+		return $ret_val;
+	}
+	
+	/**
+	 * sysRemoveGroup() - Remove a group
+	 * 
+	 * @param		int		The ID of the group to remove
+	 * @returns true on success/false on error
+	 *
+	 */
+	function sysRemoveGroup($group_id) {
+		global $sys_ldap_base_dn;
+	
+		$group = &group_get_object($group_id);
+		if (!$this->gfLdapConnect()) {
+			return false;
+		}
+	
+		//
+		//	Remove shell LDAP group
+		//
+		$ret_val=true;
+		
+		$dn = 'cn='.$group->getUnixName().',ou=Group,'.$sys_ldap_base_dn;
+	
+		if (!$this->gfLdapDelete($dn)) {
+		    $this->setError("ERROR: cannot delete LDAP group entry '".
+				 $group->getUnixName()."': ".$this->gfLdapError()."<br />");
+		    $ret_val = false;
+		}
+	
+		//
+		//	Remove CVS LDAP group
+		//
+	
+		$dn = 'cn='.$group->getUnixName().',ou=cvsGroup,'.$sys_ldap_base_dn;
+	
+		if (!$this->gfLdapDelete($dn)) {
+		    $this->setError("ERROR: cannot delete LDAP CVS group entry '".
+				 $group->getUnixName()."': ".$this->gfLdapError()."<br />");
+		    $ret_val = false;
+		}
+	
+		//
+		//	Remove AnonCVS virtual user
+		//
+	
+		$dn = 'uid=anoncvs_'.$group->getUnixName().',ou=People,'.$sys_ldap_base_dn;
+		if (!$this->gfLdapDelete($dn)) {
+		    $this->setError("ERROR: cannot delete LDAP AnonCVS user entry '".
+				 $group->getUnixName()."': ".$this->gfLdapError()."<br />");
+		    $ret_val = false;
+		}
+	
+		return $ret_val;
+	}
+	
+	/**
+	 * sysGroupAddUser() - Add a user to an LDAP group
+	 *
+	 * @param		int		The ID of the group two which the user will be added
+	 * @param		int		The ID of the user to add
+	 * @param		bool	Only add this user to CVS
+	 * @returns true on success/false on error
+	 *
+	 */
+	function sysGroupAddUser($group_id,$user_id,$cvs_only=0) {
+		global $ldap_conn;
+		global $sys_ldap_base_dn;
+	
+		$group = &group_get_object($group_id);
+		$user  = &user_get_object($user_id);
+		if (!$this->gfLdapConnect()) {
+			return false;
+		}
+		$dn = 'cn='.$group->getUnixName().',ou=Group,'.$sys_ldap_base_dn;
+		$cvs_dn = 'cn='.$group->getUnixName().',ou=cvsGroup,'.$sys_ldap_base_dn;
+		$entry['memberUid'] = $user->getUnixName();
+		
+		//
+		//	Check if user already a member of CVS group
+		//
+	
+		$res=$this->gfLdapRead($cvs_dn,"memberUid=".$user->getUnixName(),array("cn"));
+		if ($res && ldap_count_entries($ldap_conn,$res)>0) {
+			//echo "already a member of CVS<br />";
+		} else {
+			//
+			//	No, add one
+			//
+	
+			if (!$this->gfLdapModAdd($cvs_dn,$entry)) {
+				$this->setError("ERROR: cannot add member to LDAP CVS group entry '".
+				 $group->getUnixName()."': ".$this->gfLdapError()."<br />");
+				return false;
+			}
+		}
+	
+		ldap_free_result($res);
+		
+		if ($cvs_only) {
+			return true;
+		}
+		
+		//
+		//	Check if user already a member of shell group
+		//
+		$res = $this->gfLdapRead($dn, "memberUid=".$user->getUnixName(), array("cn"));
+	
+		if ($res && ldap_count_entries($ldap_conn,$res)>0) {
+			//echo "already a member<br />";
+		} else {
+			//
+			//	No, add one
+			//
+	
+			if (!$this->gfLdapModAdd($dn,$entry)) {
+				$this->setError("ERROR: cannot add member to LDAP group entry '".
+				 $group->getUnixName()."': ".$this->gfLdapError()."<br />");
+				return false;
+			}
+		}
+	
+		ldap_free_result($res);
+	
+		return true;
+	}
+	
+	/**
+	 * sysGroupRemoveUser() - Remove a user from an LDAP group
+	 *
+	 * @param		int		The ID of the group from which to remove the user
+	 * @param		int		The ID of the user to remove
+	 * @param		bool	Only remove user from CVS group
+	 * @returns true on success/false on error
+	 *
+	 */
+	function sysGroupRemoveUser($group_id,$user_id,$cvs_only=0) {
+		global $sys_ldap_base_dn;
+	
+		$group = &group_get_object($group_id);
+		$user  = &user_get_object($user_id);
+		if (!$this->gfLdapConnect()) {
+			return false;
+		}
+	
+		$dn = 'cn='.$group->getUnixName().',ou=Group,'.$sys_ldap_base_dn;
+		$cvs_dn = 'cn='.$group->getUnixName().',ou=cvsGroup,'.$sys_ldap_base_dn;
+		$entry['memberUid'] = $user->getUnixName();
+	
+		$ret_val=true;
+	
+		if (!$this->gfLdapModDel($cvs_dn,$entry) && !$this->gfLdapDoesNotExist()) {
+			$this->setError("ERROR: cannot remove member from LDAP CVS group entry '".
+				 $group->getUnixName()."': ".$this->gfLdapError()."(".$this->gfLdapErrno().")"."<br />");
+			$ret_val=false;
+		}
+		
+		if ($cvs_only) {
+			return $ret_val;
+		}
+	
+		if (!$this->gfLdapModDel($dn,$entry) && !$this->gfLdapDoesNotExist()) {
+			$this->setError("ERROR: cannot remove member from LDAP group entry '".
+				 $group->getUnixName()."': ".$this->gfLdapError()."(".$this->gfLdapErrno().")"."<br />");
+			$ret_val=false;
+		}
+		
+		return $ret_val;
+	}
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/include/system/NSSPGSQL.class
===================================================================
--- trunk/gforge_base/gforge/common/include/system/NSSPGSQL.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/system/NSSPGSQL.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,107 @@
+<?php
+/**
+ * UNIX class
+ *
+ * Class to interact with the system
+ *
+ * @version   $Id: NSSPGSQL.class 5313 2006-02-15 18:50:53Z tperdue $
+ * @author Christian Bayle
+ * @date 2004-02-05
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+require_once('common/include/System.class');
+
+class NSSPGSQL extends System {
+	/**
+        * Value to add to unix_uid to get unix uid
+	*
+	* @var  constant                $UID_ADD
+	*/
+	var $UID_ADD = 20000;
+
+	/**
+	*	NSSPGSQL() - CONSTRUCTOR
+	*
+	*/
+	function NSSPGSQL() {
+		$this->System();
+		return true;
+	}
+	/**
+ 	* sysCreateUser() - Create a user
+ 	*
+ 	* @param		int	The user ID of the user to create
+ 	* @returns The return status
+ 	*
+ 	*/
+	function sysCreateUser($user_id) {
+		return true;
+	}
+
+	/**
+ 	* sysRemoveUser() - Remove a user
+ 	*
+ 	* @param		int		The user ID of the user to remove
+ 	* @returns true on success/false on failure
+ 	*
+ 	*/
+	function sysRemoveUser($user_id) {
+		return true;
+	}
+
+	/*
+ 	* Group management functions
+ 	*/
+	
+	/**
+ 	* sysCheckGroup() - Check for the existence of a group
+ 	* 
+ 	* @param		int		The ID of the group to check
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysCheckGroup($group_id) {
+		$group =& group_get_object($group_id);
+		if (!$group){
+			return false;
+		}
+		return true;
+	}
+
+	/**
+ 	* sysCreateGroup() - Create a group
+ 	* 
+ 	* @param		int		The ID of the group to create
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysCreateGroup($group_id) {
+		$group = &group_get_object($group_id);
+		if (!$group) {
+			return false;
+		}
+		return true;
+	}
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/include/system/UNIX.class
===================================================================
--- trunk/gforge_base/gforge/common/include/system/UNIX.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/system/UNIX.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,126 @@
+<?php
+/**
+ * UNIX class
+ *
+ * Class to interact with the system
+ *
+ * @version   $Id: UNIX.class 5313 2006-02-15 18:50:53Z tperdue $
+ * @author Christian Bayle
+ * @date 2004-02-05
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+require_once('common/include/System.class');
+
+class UNIX extends System {
+	/**
+        * Value to add to unix_uid to get unix uid
+	*
+	* @var  constant                $UID_ADD
+	*/
+	var $UID_ADD = 20000;
+
+	/**
+	*	UNIX() - CONSTRUCTOR
+	*
+	*/
+	function UNIX() {
+		$this->System();
+		return true;
+	}
+	/**
+ 	* sysCreateUser() - Create a user
+ 	*
+ 	* @param		int	The user ID of the user to create
+ 	* @returns The return status
+ 	*
+ 	*/
+	function sysCreateUser($user_id) {
+		$user = &user_get_object($user_id);
+		if (!$user) {
+			return false;
+		} else {
+			$res=db_query("UPDATE users SET
+			unix_uid=user_id+".$this->UID_ADD.",
+			unix_gid=user_id+".$this->UID_ADD.",
+			unix_status='A'
+			WHERE user_id=$user_id");
+	                if (!$res) {
+	                        $this->setError('ERROR - Could Not Update User UID/GID: '.db_error());
+	                        return false;
+			}
+			return true;
+		}
+	}
+
+	/**
+ 	* sysRemoveUser() - Remove a user
+ 	*
+ 	* @param		int		The user ID of the user to remove
+ 	* @returns true on success/false on failure
+ 	*
+ 	*/
+	function sysRemoveUser($user_id) {
+		$res=db_query("UPDATE users SET unix_status='N' WHERE user_id=$user_id");
+		if (!$res) {
+			$this->setError('ERROR - Could Not Update User Unix Status: '.db_error());
+			return false;
+		}
+		return true;
+	}
+
+	/*
+ 	* Group management functions
+ 	*/
+	
+	/**
+ 	* sysCheckGroup() - Check for the existence of a group
+ 	* 
+ 	* @param		int		The ID of the group to check
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysCheckGroup($group_id) {
+		$group =& group_get_object($group_id);
+		if (!$group){
+			return false;
+		}
+		return true;
+	}
+
+	/**
+ 	* sysCreateGroup() - Create a group
+ 	* 
+ 	* @param		int		The ID of the group to create
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysCreateGroup($group_id) {
+		$group = &group_get_object($group_id);
+		if (!$group) {
+			return false;
+		}
+		return true;
+	}
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/include/system/pgsql.class
===================================================================
--- trunk/gforge_base/gforge/common/include/system/pgsql.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/system/pgsql.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,544 @@
+<?php
+/**
+ * pgsql class
+ *
+ * Class to interact with the system
+ *
+ * @version   $Id: pgsql.class 5313 2006-02-15 18:50:53Z tperdue $
+ * @author Christian Bayle
+ * @date 2004-02-05
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+require_once('common/include/System.class');
+
+class pgsql extends System {
+	/*
+ 	* Constants
+ 	*/
+
+	/**
+ 	* Value to add to unix_uid to get unix uid
+ 	*
+ 	* @var	constant		$UID_ADD
+ 	*/
+	var $UID_ADD = 20000;
+
+	/**
+ 	* Value to add to group_id to get unix gid
+ 	*
+ 	* @var	constant		$GID_ADD
+ 	*/
+	var $GID_ADD = 10000;
+
+	/**
+ 	* Value to add to unix gid to get unix uid of anoncvs special user
+ 	*
+ 	* @var	constant		$SCM_UID_ADD
+ 	*/
+	var $SCM_UID_ADD = 50000;
+
+	/**
+	*	pgsql() - CONSTRUCTOR
+	*
+	*/
+	function pgsql() {
+		$this->System();
+		return true;
+	}
+
+	/*
+ 	* User management functions
+ 	*/
+
+	/**
+ 	* sysCheckUser() - Check for the existence of a user
+ 	*
+ 	* @param		int		The user ID of the user to check
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysCheckUser($user_id) {
+		$user =& user_get_object($user_id);
+		if (!$user) {
+			return false;
+		}
+		return true;
+	}
+
+	/**
+ 	* sysCreateUser() - Create a user
+ 	*
+ 	* @param		int	The user ID of the user to create
+ 	* @returns The return status
+ 	*
+ 	*/
+	function sysCreateUser($user_id) {
+		$user = &user_get_object($user_id);
+		if (!$user) {
+			return false;
+		} else {
+			$res=db_query("UPDATE users SET
+			unix_uid=user_id+".$this->UID_ADD.",
+			unix_gid=user_id+".$this->UID_ADD.",
+			unix_status='A'
+			WHERE user_id=$user_id");
+	                if (!$res) {
+	                        $this->setError('ERROR - Could Not Update User UID/GID: '.db_error());
+	                        return false;
+			} else {
+				$query="DELETE FROM nss_usergroups WHERE user_id=$user_id";
+				$res1=db_query($query);
+	                	if (!$res1) {
+					$this->setError('ERROR - Could Not Delete Group Member(s): '.db_error());
+	                        	return false;
+				}
+				// This is group used for user, not a real project
+				$query="DELETE FROM nss_groups WHERE name IN
+					(SELECT user_name FROM users WHERE user_id=$user_id)";
+				$res2=db_query($query);
+	                	if (!$res2) {
+	                        	$this->setError('ERROR - Could Not Delete Group GID: '.db_error());
+	                        	return false;
+				}
+				$query="INSERT INTO nss_groups
+					(user_id, group_id,name, gid)
+					SELECT user_id, 0, user_name, unix_gid
+					FROM users WHERE user_id=$user_id";
+				$res3=db_query($query);
+	                	if (!$res3) {
+	                        	$this->setError('ERROR - Could Not Update Group GID: '.db_error());
+	                        	return false;
+				}
+				$query="INSERT INTO nss_usergroups (
+					SELECT
+						users.unix_uid AS uid,
+						groups.group_id + ".$this->GID_ADD." AS gid,
+						users.user_id AS user_id,
+						groups.group_id AS group_id,
+						users.user_name AS user_name,
+						groups.unix_group_name AS unix_group_name
+					FROM users,groups,user_group
+					WHERE
+						users.user_id=user_group.user_id
+					AND
+						groups.group_id=user_group.group_id
+					AND
+						users.user_id=$user_id
+					AND
+						groups.status = 'A'
+					AND
+						users.unix_status='A'
+					AND
+						users.status = 'A'
+					UNION
+					SELECT
+						users.unix_uid AS uid,
+						groups.group_id + ".$this->SCM_UID_ADD." AS gid,
+						users.user_id AS user_id,
+						groups.group_id AS group_id,
+						users.user_name AS user_name,
+						'scm_' || groups.unix_group_name AS unix_group_name
+					FROM users,groups,user_group
+					WHERE
+						users.user_id=user_group.user_id
+					AND
+						groups.group_id=user_group.group_id
+					AND
+						users.user_id=$user_id
+					AND
+						groups.status = 'A'
+					AND
+						users.unix_status='A'
+					AND
+						users.status = 'A'
+					AND
+						user_group.cvs_flags > 0)
+				";
+				$res4=db_query($query);
+	                	if (!$res4) {
+	                        	$this->setError('ERROR - Could Not Update Group Member(s): '.db_error());
+	                        	return false;
+				}
+			}
+			return true;
+		}
+	}
+
+	/**
+ 	* sysCheckCreateUser() - Check that a user has been created
+ 	*
+ 	* @param		int		The ID of the user to check
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysCheckCreateUser($user_id) {
+		return $this->sysCreateUser($user_id);
+	}
+
+	/**
+ 	* sysCheckCreateGroup() - Check that a group has been created
+ 	*
+ 	* @param		int		The ID of the user to check
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysCheckCreateGroup($user_id) {
+		return $this->sysCreateGroup($user_id);
+	}
+
+	/**
+ 	* sysRemoveUser() - Remove a user
+ 	*
+ 	* @param		int		The user ID of the user to remove
+ 	* @returns true on success/false on failure
+ 	*
+ 	*/
+	function sysRemoveUser($user_id) {
+		$res=db_query("UPDATE users SET unix_status='N' WHERE user_id=$user_id");
+		if (!$res) {
+			$this->setError('ERROR - Could Not Update User Unix Status: '.db_error());
+			return false;
+		} else {
+			$query="DELETE FROM nss_usergroups WHERE user_id=$user_id";
+			$res1=db_query($query);
+			if (!$res1) {
+				$this->setError('ERROR - Could Not Delete Group Member(s): '.db_error());
+				return false;
+			}
+			// This is group used for user, not a real project
+			$query="DELETE FROM nss_groups WHERE name IN
+				(SELECT user_name FROM users WHERE user_id=$user_id)";
+			$res2=db_query($query);
+			if (!$res2) {
+				$this->setError('ERROR - Could Not Delete Group GID: '.db_error());
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+ 	* sysUserSetAttribute() - Set an attribute for a user
+ 	*
+ 	* @param		int		The user ID
+ 	* @param		string	The attribute to set
+ 	* @param		string	The new value of the attribute
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysUserSetAttribute($user_id,$attr,$value) {
+		return true;
+	}
+
+	/*
+ 	* Group management functions
+ 	*/
+
+	/**
+ 	* sysCheckGroup() - Check for the existence of a group
+ 	*
+ 	* @param		int		The ID of the group to check
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysCheckGroup($group_id) {
+		$group =& group_get_object($group_id);
+		if (!$group){
+			return false;
+		} else {
+			$query="SELECT group_id FROM nss_groups WHERE group_id=$group_id";
+			$res=db_query($query);
+			if (db_numrows($res) == 0){
+				return false;
+			} else {
+				return true;
+			}
+		}
+	}
+
+	/**
+ 	* sysCreateGroup() - Create a group
+ 	*
+ 	* @param		int		The ID of the group to create
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysCreateGroup($group_id) {
+		$group = &group_get_object($group_id);
+		if (!$group) {
+			return false;
+		} else {
+				$query="DELETE FROM nss_usergroups WHERE group_id=$group_id";
+				$res1=db_query($query);
+	                	if (!$res1) {
+					$this->setError('ERROR - Could Not Delete Group Member(s): '.db_error());
+	                        	return false;
+				}
+				$query="DELETE FROM nss_groups WHERE group_id=$group_id";
+				$res3=db_query($query);
+	                	if (!$res3) {
+	                        	$this->setError('ERROR - Could Not Delete Group GID: '.db_error());
+	                        	return false;
+				}
+				$query="INSERT INTO nss_groups
+					(user_id, group_id, name, gid)
+        				SELECT 0, group_id, unix_group_name, group_id +".$this->GID_ADD."
+					FROM groups
+					WHERE group_id=$group_id
+					";
+				$res4=db_query($query);
+	                	if (!$res4) {
+	                        	$this->setError('ERROR - Could Not Insert Group GID: '.db_error());
+	                        	return false;
+				}
+				$query="INSERT INTO nss_groups
+					(user_id, group_id, name, gid)
+        				SELECT 0, group_id, 'scm_' || unix_group_name, group_id +".$this->SCM_UID_ADD."
+					FROM groups
+					WHERE group_id=$group_id
+					";
+				$res5=db_query($query);
+	                	if (!$res5) {
+	                        	$this->setError('ERROR - Could Not Insert SCM Group GID: '.db_error());
+	                        	return false;
+				}
+
+				/*
+				 * Add user "maven" to all groups
+				 * -- tg at aurisp.de
+				 */
+
+				$query="INSERT INTO nss_usergroups (
+					SELECT
+						200 AS uid,
+						groups.group_id + ".$this->GID_ADD." AS gid,
+						0 AS user_id,
+						groups.group_id AS group_id,
+						'maven' AS user_name,
+						groups.unix_group_name AS unix_group_name
+					FROM groups
+					WHERE group_id=$group_id
+					);";
+				$res5=db_query($query);
+	                	if (!$res5) {
+	                        	$this->setError('ERROR - Could Not Insert Maven: '.db_error());
+	                        	return false;
+				}
+
+				$query="INSERT INTO nss_usergroups (
+					SELECT
+						users.unix_uid AS uid,
+						groups.group_id + ".$this->GID_ADD." AS gid,
+						users.user_id AS user_id,
+						groups.group_id AS group_id,
+						users.user_name AS user_name,
+						groups.unix_group_name AS unix_group_name
+					FROM users,groups,user_group
+					WHERE
+						users.user_id=user_group.user_id
+					AND
+						groups.group_id=user_group.group_id
+					AND
+						groups.group_id=$group_id
+					AND
+						groups.status = 'A'
+					AND
+						users.unix_status='A'
+					AND
+						users.status = 'A'
+					UNION
+					SELECT
+						users.unix_uid AS uid,
+						groups.group_id + ".$this->SCM_UID_ADD." AS gid,
+						users.user_id AS user_id,
+						groups.group_id AS group_id,
+						users.user_name AS user_name,
+						'scm_' || groups.unix_group_name AS unix_group_name
+					FROM users,groups,user_group
+					WHERE
+						groups.group_id=user_group.group_id
+					AND
+						users.user_id=user_group.user_id
+					AND
+						groups.group_id=$group_id
+					AND
+						groups.status = 'A'
+					AND
+						users.unix_status='A'
+					AND
+						users.status = 'A'
+					AND
+						user_group.cvs_flags > 0);
+				";
+				$res6=db_query($query);
+	                	if (!$res6) {
+	                        	$this->setError('ERROR - Could Not Update Group Member(s): '.db_error());
+	                        	return false;
+				}
+		}
+		return true;
+	}
+
+	/**
+ 	* sysRemoveGroup() - Remove a group
+ 	*
+ 	* @param		int		The ID of the group to remove
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysRemoveGroup($group_id) {
+		$query="DELETE FROM nss_usergroups WHERE group_id=$group_id";
+//echo "<h2>SYS::sysRemoveGroup: $query</h2>";
+		$res1=db_query($query);
+		if (!$res1) {
+			$this->setError('ERROR - Could Not Delete Group Member(s): '.db_error());
+			return false;
+		}
+		$query="DELETE FROM nss_groups WHERE group_id=$group_id ";
+//echo "<h2>SYS::sysRemoveGroup: $query</h2>";
+		$res3=db_query($query);
+	              	if (!$res3) {
+	                      	$this->setError('ERROR - Could Not Delete Group GID: '.db_error());
+	                      	return false;
+		}
+		return true;
+	}
+
+	/**
+ 	* sysGroupAddUser() - Add a user to a group
+ 	*
+ 	* @param		int		The ID of the group two which the user will be added
+ 	* @param		int		The ID of the user to add
+ 	* @param		bool	Only add this user to CVS
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysGroupAddUser($group_id,$user_id,$cvs_only=0) {
+		if ($cvs_only) {
+			$query="DELETE FROM nss_usergroups WHERE user_id=$user_id AND group_id=$group_id
+			AND unix_group_name LIKE 'scm_%'";
+		} else {
+			$query="DELETE FROM nss_usergroups WHERE user_id=$user_id AND group_id=$group_id";
+		}
+//echo "<h2>SYS::sysGroupAddUser DELETE: $query</h2>";
+		$res0=db_query($query);
+		if (!$res0) {
+			$this->setError('ERROR - Could Not Delete Group Member(s): '.db_error());
+			return false;
+		}
+		$query="INSERT INTO nss_usergroups (
+			SELECT
+				users.unix_uid AS uid,
+				groups.group_id + ".$this->SCM_UID_ADD." AS gid,
+				users.user_id AS user_id,
+				groups.group_id AS group_id,
+				users.user_name AS user_name,
+				'scm_' || groups.unix_group_name AS unix_group_name
+			FROM users,groups,user_group
+			WHERE
+				users.user_id=user_group.user_id
+			AND
+				groups.group_id=user_group.group_id
+			AND
+				users.user_id=$user_id
+			AND
+				groups.group_id=$group_id
+			AND
+				groups.status = 'A'
+			AND
+				users.unix_status='A'
+			AND
+				users.status = 'A'
+			AND
+				user_group.cvs_flags > 0) ";
+//echo "<h2>SYS::sysGroupAddUser ADDCVS: $query</h2>";
+		$res1=db_query($query);
+		if (!$res1) {
+			$this->setError('ERROR - Could Not Add SCM Member(s): '.db_error());
+			return false;
+		}
+
+		if ($cvs_only) {
+			return true;
+		}
+
+		$query="INSERT INTO nss_usergroups (
+			SELECT
+				users.unix_uid AS uid,
+				groups.group_id + ".$this->GID_ADD." AS gid,
+				users.user_id AS user_id,
+				groups.group_id AS group_id,
+				users.user_name AS user_name,
+				groups.unix_group_name AS unix_group_name
+			FROM users,groups,user_group
+			WHERE
+				users.user_id=user_group.user_id
+			AND
+				groups.group_id=user_group.group_id
+			AND
+				users.user_id=$user_id
+			AND
+				groups.group_id=$group_id
+			AND
+				groups.status = 'A'
+			AND
+				users.unix_status='A'
+			AND
+				users.status = 'A') ";
+//echo "<h2>SYS::sysGroupAddUser ADDSYS: $query</h2>";
+		$res2=db_query($query);
+		if (!$res2) {
+			$this->setError('ERROR - Could Not Add Shell Group Member(s): '.db_error());
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
+ 	* sysGroupRemoveUser() - Remove a user from a group
+ 	*
+ 	* @param		int		The ID of the group from which to remove the user
+ 	* @param		int		The ID of the user to remove
+ 	* @param		bool	Only remove user from CVS group
+ 	* @returns true on success/false on error
+ 	*
+ 	*/
+	function sysGroupRemoveUser($group_id,$user_id,$cvs_only=0) {
+		if ($cvs_only) {
+			$query="DELETE FROM nss_usergroups WHERE group_id=$group_id AND user_id=$user_id
+			AND unix_group_name LIKE 'scm_%'";
+		} else {
+			$query="DELETE FROM nss_usergroups WHERE group_id=$group_id AND user_id=$user_id";
+		}
+//echo "<h2>SYS::sysGroupRemoveUser REM: $query</h2>";
+		$res1=db_query($query);
+		if (!$res1) {
+			$this->setError('ERROR - Could Not Delete Group Member(s): '.db_error());
+			return false;
+		}
+		return true;
+	}
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/include/timezones.php
===================================================================
--- trunk/gforge_base/gforge/common/include/timezones.php	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/timezones.php	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,489 @@
+<?php
+/**
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+
+$TZs[]='US/Alaska';
+$TZs[]='US/Aleutian';
+$TZs[]='US/Arizona';
+$TZs[]='US/Central';
+$TZs[]='US/Eastern';
+$TZs[]='US/East-Indiana';
+$TZs[]='US/Hawaii';
+$TZs[]='US/Indiana-Starke';
+$TZs[]='US/Michigan';
+$TZs[]='US/Mountain';
+$TZs[]='US/Pacific';
+$TZs[]='US/Samoa';
+$TZs[]='Africa/Abidjan';
+$TZs[]='Africa/Accra';
+$TZs[]='Africa/Addis_Ababa';
+$TZs[]='Africa/Algiers';
+$TZs[]='Africa/Asmera';
+$TZs[]='Africa/Bamako';
+$TZs[]='Africa/Bangui';
+$TZs[]='Africa/Banjul';
+$TZs[]='Africa/Bissau';
+$TZs[]='Africa/Blantyre';
+$TZs[]='Africa/Brazzaville';
+$TZs[]='Africa/Bujumbura';
+$TZs[]='Africa/Cairo';
+$TZs[]='Africa/Casablanca';
+$TZs[]='Africa/Ceuta';
+$TZs[]='Africa/Conakry';
+$TZs[]='Africa/Dakar';
+$TZs[]='Africa/Dar_es_Salaam';
+$TZs[]='Africa/Djibouti';
+$TZs[]='Africa/Douala';
+$TZs[]='Africa/El_Aaiun';
+$TZs[]='Africa/Freetown';
+$TZs[]='Africa/Gaborone';
+$TZs[]='Africa/Harare';
+$TZs[]='Africa/Johannesburg';
+$TZs[]='Africa/Kampala';
+$TZs[]='Africa/Khartoum';
+$TZs[]='Africa/Kigali';
+$TZs[]='Africa/Kinshasa';
+$TZs[]='Africa/Lagos';
+$TZs[]='Africa/Libreville';
+$TZs[]='Africa/Lome';
+$TZs[]='Africa/Luanda';
+$TZs[]='Africa/Lubumbashi';
+$TZs[]='Africa/Lusaka';
+$TZs[]='Africa/Malabo';
+$TZs[]='Africa/Maputo';
+$TZs[]='Africa/Maseru';
+$TZs[]='Africa/Mbabane';
+$TZs[]='Africa/Mogadishu';
+$TZs[]='Africa/Monrovia';
+$TZs[]='Africa/Nairobi';
+$TZs[]='Africa/Ndjamena';
+$TZs[]='Africa/Niamey';
+$TZs[]='Africa/Nouakchott';
+$TZs[]='Africa/Ouagadougou';
+$TZs[]='Africa/Porto-Novo';
+$TZs[]='Africa/Sao_Tome';
+$TZs[]='Africa/Timbuktu';
+$TZs[]='Africa/Tripoli';
+$TZs[]='Africa/Tunis';
+$TZs[]='Africa/Windhoek';
+$TZs[]='America/Adak';
+$TZs[]='America/Anchorage';
+$TZs[]='America/Anguilla';
+$TZs[]='America/Antigua';
+$TZs[]='America/Araguaina';
+$TZs[]='America/Aruba';
+$TZs[]='America/Asuncion';
+$TZs[]='America/Atka';
+$TZs[]='America/Barbados';
+$TZs[]='America/Belem';
+$TZs[]='America/Belize';
+$TZs[]='America/Boa_Vista';
+$TZs[]='America/Bogota';
+$TZs[]='America/Boise';
+$TZs[]='America/Buenos_Aires';
+$TZs[]='America/Cambridge_Bay';
+$TZs[]='America/Cancun';
+$TZs[]='America/Caracas';
+$TZs[]='America/Catamarca';
+$TZs[]='America/Cayenne';
+$TZs[]='America/Cayman';
+$TZs[]='America/Chicago';
+$TZs[]='America/Chihuahua';
+$TZs[]='America/Cordoba';
+$TZs[]='America/Costa_Rica';
+$TZs[]='America/Cuiaba';
+$TZs[]='America/Curacao';
+$TZs[]='America/Dawson';
+$TZs[]='America/Dawson_Creek';
+$TZs[]='America/Denver';
+$TZs[]='America/Detroit';
+$TZs[]='America/Dominica';
+$TZs[]='America/Edmonton';
+$TZs[]='America/El_Salvador';
+$TZs[]='America/Ensenada';
+$TZs[]='America/Fortaleza';
+$TZs[]='America/Fort_Wayne';
+$TZs[]='America/Glace_Bay';
+$TZs[]='America/Godthab';
+$TZs[]='America/Goose_Bay';
+$TZs[]='America/Grand_Turk';
+$TZs[]='America/Grenada';
+$TZs[]='America/Guadeloupe';
+$TZs[]='America/Guatemala';
+$TZs[]='America/Guayaquil';
+$TZs[]='America/Guyana';
+$TZs[]='America/Halifax';
+$TZs[]='America/Havana';
+$TZs[]='America/Hermosillo';
+$TZs[]='America/Indiana/Indianapolis';
+$TZs[]='America/Indiana/Knox';
+$TZs[]='America/Indiana/Marengo';
+$TZs[]='America/Indiana/Vevay';
+$TZs[]='America/Indianapolis';
+$TZs[]='America/Inuvik';
+$TZs[]='America/Iqaluit';
+$TZs[]='America/Jamaica';
+$TZs[]='America/Jujuy';
+$TZs[]='America/Juneau';
+$TZs[]='America/Knox_IN';
+$TZs[]='America/La_Paz';
+$TZs[]='America/Lima';
+$TZs[]='America/Los_Angeles';
+$TZs[]='America/Louisville';
+$TZs[]='America/Maceio';
+$TZs[]='America/Managua';
+$TZs[]='America/Manaus';
+$TZs[]='America/Martinique';
+$TZs[]='America/Mazatlan';
+$TZs[]='America/Mendoza';
+$TZs[]='America/Menominee';
+$TZs[]='America/Mexico_City';
+$TZs[]='America/Miquelon';
+$TZs[]='America/Montevideo';
+$TZs[]='America/Montreal';
+$TZs[]='America/Montserrat';
+$TZs[]='America/Nassau';
+$TZs[]='America/New_York';
+$TZs[]='America/Nipigon';
+$TZs[]='America/Nome';
+$TZs[]='America/Noronha';
+$TZs[]='America/Panama';
+$TZs[]='America/Pangnirtung';
+$TZs[]='America/Paramaribo';
+$TZs[]='America/Phoenix';
+$TZs[]='America/Port-au-Prince';
+$TZs[]='America/Porto_Acre';
+$TZs[]='America/Port_of_Spain';
+$TZs[]='America/Porto_Velho';
+$TZs[]='America/Puerto_Rico';
+$TZs[]='America/Rainy_River';
+$TZs[]='America/Rankin_Inlet';
+$TZs[]='America/Regina';
+$TZs[]='America/Rosario';
+$TZs[]='America/Santiago';
+$TZs[]='America/Santo_Domingo';
+$TZs[]='America/Sao_Paulo';
+$TZs[]='America/Scoresbysund';
+$TZs[]='America/Shiprock';
+$TZs[]='America/St_Johns';
+$TZs[]='America/St_Kitts';
+$TZs[]='America/St_Lucia';
+$TZs[]='America/St_Thomas';
+$TZs[]='America/St_Vincent';
+$TZs[]='America/Swift_Current';
+$TZs[]='America/Tegucigalpa';
+$TZs[]='America/Thule';
+$TZs[]='America/Thunder_Bay';
+$TZs[]='America/Tijuana';
+$TZs[]='America/Tortola';
+$TZs[]='America/Vancouver';
+$TZs[]='America/Virgin';
+$TZs[]='America/Whitehorse';
+$TZs[]='America/Winnipeg';
+$TZs[]='America/Yakutat';
+$TZs[]='America/Yellowknife';
+$TZs[]='Antarctica/Casey';
+$TZs[]='Antarctica/Davis';
+$TZs[]='Antarctica/DumontDUrville';
+$TZs[]='Antarctica/Mawson';
+$TZs[]='Antarctica/McMurdo';
+$TZs[]='Antarctica/Palmer';
+$TZs[]='Antarctica/South_Pole';
+$TZs[]='Antarctica/Syowa';
+$TZs[]='Arctic/Longyearbyen';
+$TZs[]='Asia/Aden';
+$TZs[]='Asia/Almaty';
+$TZs[]='Asia/Amman';
+$TZs[]='Asia/Anadyr';
+$TZs[]='Asia/Aqtau';
+$TZs[]='Asia/Aqtobe';
+$TZs[]='Asia/Ashkhabad';
+$TZs[]='Asia/Baghdad';
+$TZs[]='Asia/Bahrain';
+$TZs[]='Asia/Baku';
+$TZs[]='Asia/Bangkok';
+$TZs[]='Asia/Beirut';
+$TZs[]='Asia/Bishkek';
+$TZs[]='Asia/Brunei';
+$TZs[]='Asia/Calcutta';
+$TZs[]='Asia/Chungking';
+$TZs[]='Asia/Colombo';
+$TZs[]='Asia/Dacca';
+$TZs[]='Asia/Damascus';
+$TZs[]='Asia/Dili';
+$TZs[]='Asia/Dubai';
+$TZs[]='Asia/Dushanbe';
+$TZs[]='Asia/Gaza';
+$TZs[]='Asia/Harbin';
+$TZs[]='Asia/Hong_Kong';
+$TZs[]='Asia/Hovd';
+$TZs[]='Asia/Irkutsk';
+$TZs[]='Asia/Istanbul';
+$TZs[]='Asia/Jakarta';
+$TZs[]='Asia/Jayapura';
+$TZs[]='Asia/Jerusalem';
+$TZs[]='Asia/Kabul';
+$TZs[]='Asia/Kamchatka';
+$TZs[]='Asia/Karachi';
+$TZs[]='Asia/Kashgar';
+$TZs[]='Asia/Katmandu';
+$TZs[]='Asia/Krasnoyarsk';
+$TZs[]='Asia/Kuala_Lumpur';
+$TZs[]='Asia/Kuching';
+$TZs[]='Asia/Kuwait';
+$TZs[]='Asia/Macao';
+$TZs[]='Asia/Magadan';
+$TZs[]='Asia/Manila';
+$TZs[]='Asia/Muscat';
+$TZs[]='Asia/Nicosia';
+$TZs[]='Asia/Novosibirsk';
+$TZs[]='Asia/Omsk';
+$TZs[]='Asia/Phnom_Penh';
+$TZs[]='Asia/Pyongyang';
+$TZs[]='Asia/Qatar';
+$TZs[]='Asia/Rangoon';
+$TZs[]='Asia/Riyadh';
+$TZs[]='Asia/Riyadh87';
+$TZs[]='Asia/Riyadh88';
+$TZs[]='Asia/Riyadh89';
+$TZs[]='Asia/Saigon';
+$TZs[]='Asia/Samarkand';
+$TZs[]='Asia/Seoul';
+$TZs[]='Asia/Shanghai';
+$TZs[]='Asia/Singapore';
+$TZs[]='Asia/Taipei';
+$TZs[]='Asia/Tashkent';
+$TZs[]='Asia/Tbilisi';
+$TZs[]='Asia/Tehran';
+$TZs[]='Asia/Tel_Aviv';
+$TZs[]='Asia/Thimbu';
+$TZs[]='Asia/Tokyo';
+$TZs[]='Asia/Ujung_Pandang';
+$TZs[]='Asia/Ulaanbaatar';
+$TZs[]='Asia/Ulan_Bator';
+$TZs[]='Asia/Urumqi';
+$TZs[]='Asia/Vientiane';
+$TZs[]='Asia/Vladivostok';
+$TZs[]='Asia/Yakutsk';
+$TZs[]='Asia/Yekaterinburg';
+$TZs[]='Asia/Yerevan';
+$TZs[]='Atlantic/Azores';
+$TZs[]='Atlantic/Bermuda';
+$TZs[]='Atlantic/Canary';
+$TZs[]='Atlantic/Cape_Verde';
+$TZs[]='Atlantic/Faeroe';
+$TZs[]='Atlantic/Jan_Mayen';
+$TZs[]='Atlantic/Madeira';
+$TZs[]='Atlantic/Reykjavik';
+$TZs[]='Atlantic/South_Georgia';
+$TZs[]='Atlantic/Stanley';
+$TZs[]='Atlantic/St_Helena';
+$TZs[]='Australia/ACT';
+$TZs[]='Australia/Adelaide';
+$TZs[]='Australia/Brisbane';
+$TZs[]='Australia/Broken_Hill';
+$TZs[]='Australia/Canberra';
+$TZs[]='Australia/Darwin';
+$TZs[]='Australia/Hobart';
+$TZs[]='Australia/LHI';
+$TZs[]='Australia/Lindeman';
+$TZs[]='Australia/Lord_Howe';
+$TZs[]='Australia/Melbourne';
+$TZs[]='Australia/North';
+$TZs[]='Australia/NSW';
+$TZs[]='Australia/Perth';
+$TZs[]='Australia/Queensland';
+$TZs[]='Australia/South';
+$TZs[]='Australia/Sydney';
+$TZs[]='Australia/Tasmania';
+$TZs[]='Australia/Victoria';
+$TZs[]='Australia/West';
+$TZs[]='Australia/Yancowinna';
+$TZs[]='Brazil/Acre';
+$TZs[]='Brazil/DeNoronha';
+$TZs[]='Brazil/East';
+$TZs[]='Brazil/West';
+$TZs[]='Canada/Atlantic';
+$TZs[]='Canada/Central';
+$TZs[]='Canada/Eastern';
+$TZs[]='Canada/East-Saskatchewan';
+$TZs[]='Canada/Mountain';
+$TZs[]='Canada/Newfoundland';
+$TZs[]='Canada/Pacific';
+$TZs[]='Canada/Saskatchewan';
+$TZs[]='Canada/Yukon';
+$TZs[]='CET';
+$TZs[]='Chile/Continental';
+$TZs[]='Chile/EasterIsland';
+$TZs[]='China/Beijing';
+$TZs[]='China/Shanghai';
+$TZs[]='CST6CDT';
+$TZs[]='Cuba';
+$TZs[]='EET';
+$TZs[]='Egypt';
+$TZs[]='Eire';
+$TZs[]='EST';
+$TZs[]='EST5EDT';
+$TZs[]='Europe/Amsterdam';
+$TZs[]='Europe/Andorra';
+$TZs[]='Europe/Athens';
+$TZs[]='Europe/Belfast';
+$TZs[]='Europe/Belgrade';
+$TZs[]='Europe/Berlin';
+$TZs[]='Europe/Bratislava';
+$TZs[]='Europe/Brussels';
+$TZs[]='Europe/Bucharest';
+$TZs[]='Europe/Budapest';
+$TZs[]='Europe/Chisinau';
+$TZs[]='Europe/Copenhagen';
+$TZs[]='Europe/Dublin';
+$TZs[]='Europe/Gibraltar';
+$TZs[]='Europe/Helsinki';
+$TZs[]='Europe/Istanbul';
+$TZs[]='Europe/Kaliningrad';
+$TZs[]='Europe/Kiev';
+$TZs[]='Europe/Lisbon';
+$TZs[]='Europe/Ljubljana';
+$TZs[]='Europe/London';
+$TZs[]='Europe/Luxembourg';
+$TZs[]='Europe/Madrid';
+$TZs[]='Europe/Malta';
+$TZs[]='Europe/Minsk';
+$TZs[]='Europe/Monaco';
+$TZs[]='Europe/Moscow';
+$TZs[]='Europe/Oslo';
+$TZs[]='Europe/Paris';
+$TZs[]='Europe/Prague';
+$TZs[]='Europe/Riga';
+$TZs[]='Europe/Rome';
+$TZs[]='Europe/Samara';
+$TZs[]='Europe/San_Marino';
+$TZs[]='Europe/Sarajevo';
+$TZs[]='Europe/Simferopol';
+$TZs[]='Europe/Skopje';
+$TZs[]='Europe/Sofia';
+$TZs[]='Europe/Stockholm';
+$TZs[]='Europe/Tallinn';
+$TZs[]='Europe/Tirane';
+$TZs[]='Europe/Tiraspol';
+$TZs[]='Europe/Uzhgorod';
+$TZs[]='Europe/Vaduz';
+$TZs[]='Europe/Vatican';
+$TZs[]='Europe/Vienna';
+$TZs[]='Europe/Vilnius';
+$TZs[]='Europe/Warsaw';
+$TZs[]='Europe/Zagreb';
+$TZs[]='Europe/Zaporozhye';
+$TZs[]='Europe/Zurich';
+$TZs[]='Factory';
+$TZs[]='GB';
+$TZs[]='GB-Eire';
+$TZs[]='GMT';
+$TZs[]='GMT0';
+$TZs[]='GMT-0';
+$TZs[]='GMT+0';
+$TZs[]='Greenwich';
+$TZs[]='Hongkong';
+$TZs[]='HST';
+$TZs[]='Iceland';
+$TZs[]='Indian/Antananarivo';
+$TZs[]='Indian/Chagos';
+$TZs[]='Indian/Christmas';
+$TZs[]='Indian/Cocos';
+$TZs[]='Indian/Comoro';
+$TZs[]='Indian/Kerguelen';
+$TZs[]='Indian/Mahe';
+$TZs[]='Indian/Maldives';
+$TZs[]='Indian/Mauritius';
+$TZs[]='Indian/Mayotte';
+$TZs[]='Indian/Reunion';
+$TZs[]='Iran';
+$TZs[]='Israel';
+$TZs[]='Jamaica';
+$TZs[]='Japan';
+$TZs[]='Kwajalein';
+$TZs[]='Libya';
+$TZs[]='MET';
+$TZs[]='Mexico/BajaNorte';
+$TZs[]='Mexico/BajaSur';
+$TZs[]='Mexico/General';
+$TZs[]='Mideast/Riyadh87';
+$TZs[]='Mideast/Riyadh88';
+$TZs[]='Mideast/Riyadh89';
+$TZs[]='MST';
+$TZs[]='MST7MDT';
+$TZs[]='Navajo';
+$TZs[]='NZ';
+$TZs[]='NZ-CHAT';
+$TZs[]='Pacific/Apia';
+$TZs[]='Pacific/Auckland';
+$TZs[]='Pacific/Chatham';
+$TZs[]='Pacific/Easter';
+$TZs[]='Pacific/Efate';
+$TZs[]='Pacific/Enderbury';
+$TZs[]='Pacific/Fakaofo';
+$TZs[]='Pacific/Fiji';
+$TZs[]='Pacific/Funafuti';
+$TZs[]='Pacific/Galapagos';
+$TZs[]='Pacific/Gambier';
+$TZs[]='Pacific/Guadalcanal';
+$TZs[]='Pacific/Guam';
+$TZs[]='Pacific/Honolulu';
+$TZs[]='Pacific/Johnston';
+$TZs[]='Pacific/Kiritimati';
+$TZs[]='Pacific/Kosrae';
+$TZs[]='Pacific/Kwajalein';
+$TZs[]='Pacific/Majuro';
+$TZs[]='Pacific/Marquesas';
+$TZs[]='Pacific/Midway';
+$TZs[]='Pacific/Nauru';
+$TZs[]='Pacific/Niue';
+$TZs[]='Pacific/Norfolk';
+$TZs[]='Pacific/Noumea';
+$TZs[]='Pacific/Pago_Pago';
+$TZs[]='Pacific/Palau';
+$TZs[]='Pacific/Pitcairn';
+$TZs[]='Pacific/Ponape';
+$TZs[]='Pacific/Port_Moresby';
+$TZs[]='Pacific/Rarotonga';
+$TZs[]='Pacific/Saipan';
+$TZs[]='Pacific/Samoa';
+$TZs[]='Pacific/Tahiti';
+$TZs[]='Pacific/Tarawa';
+$TZs[]='Pacific/Tongatapu';
+$TZs[]='Pacific/Truk';
+$TZs[]='Pacific/Wake';
+$TZs[]='Pacific/Wallis';
+$TZs[]='Pacific/Yap';
+$TZs[]='Poland';
+$TZs[]='Portugal';
+$TZs[]='PRC';
+$TZs[]='PST8PDT';
+$TZs[]='ROC';
+$TZs[]='ROK';
+$TZs[]='Singapore';
+$TZs[]='Turkey';
+$TZs[]='UCT';
+$TZs[]='Universal';
+$TZs[]='UTC';
+$TZs[]='WET';
+$TZs[]='W-SU';
+$TZs[]='Zulu';
+
+?>

Added: trunk/gforge_base/gforge/common/include/utils.php
===================================================================
--- trunk/gforge_base/gforge/common/include/utils.php	                        (rev 0)
+++ trunk/gforge_base/gforge/common/include/utils.php	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,923 @@
+<?php
+/**
+ * utils.php - Misc utils common to all aspects of the site
+ *
+ * Copyright 1999-2001 (c) VA Linux Systems
+ *
+ * @version   $Id: utils.php 5733 2006-09-30 21:14:41Z marcelo $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+
+
+/**
+ * removeCRLF() - remove any Carriage Return-Line Feed from a string. 
+ * That function is useful to remove the possibility of a CRLF Injection when sending mail
+ * All the data that we will send should be passed through that function
+ *
+ * @param	   string  The string that we want to empty from any CRLF 
+ */
+function util_remove_CRLF($str) {
+	return strtr($str, "\015\012", '  ');
+}
+
+
+/**
+ * util_check_fileupload() - determines if a filename is appropriate for upload
+ *
+ * @param	   string  The name of the file being uploaded
+ */
+function util_check_fileupload($filename) {
+
+	/* Empty file is a valid file.
+	This is because this function should be called
+	unconditionally at the top of submit action processing
+	and many forms have optional file upload. */
+	if ($filename == 'none' || $filename == '') {
+		return true;
+	}
+
+	/* This should be enough... */
+	if (!is_uploaded_file($filename)) {
+		return false;
+	}
+	/* ... but we'd rather be paranoic */
+	if (strstr($filename, '..')) {
+		return false;
+	}
+	if (!is_file($filename)) {
+		return false;
+	}
+	if (!file_exists($filename)) {
+		return false;
+	}
+	if ((dirname($filename) != '/tmp') &&
+            (dirname($filename) != "/var/tmp")) {
+		return false;
+	}
+	return true;
+}
+
+/**
+ * util_send_message() - Send email
+ * This function should be used in place of the PHP mail() function
+ *
+ * @param		string	The email recipients address
+ * @param		string	The email subject
+ * @param		string	The body of the email message
+ * @param		string	The optional email sender address.  Defaults to 'noreply@'
+ * @param		string	The addresses to blind-carbon-copy this message
+ * @param		string	The optional email sender name. Defaults to ''
+ *
+ */
+function util_send_message($to,$subject,$body,$from='',$BCC='',$sendername='',$extra_headers='') {
+	global $Language,$sys_bcc_all_email_address,$sys_sendmail_path;
+
+	if (!$to) {
+		$to='noreply@'.$GLOBALS['sys_default_domain'];
+	}
+	if (!$from) {
+		$from='noreply@'.$GLOBALS['sys_default_domain'];
+	}
+	
+
+	$charset = $Language->getText('conf','mail_charset');
+	if (!$charset) {
+		$charset = 'UTF-8';
+	}
+	$body2 = '';
+	if ($extra_headers) {
+		$body2 .= $extra_headers."\n";
+	}
+	$body2 .= "To: $to".
+		"\nFrom: ".util_encode_mailaddr($from,$sendername,$charset);
+	if (!empty($sys_bcc_all_email_address)) {
+		$BCC.=",$sys_bcc_all_email_address";
+	}
+	if(!empty($BCC)) {
+		$body2 .= "\nBCC: $BCC";
+	}
+	$body2 .= "\nSubject: ".util_encode_mimeheader($subject, $charset).
+		"\nContent-type: text/plain; charset=$charset".
+		"\n\n".
+		util_convert_body($body, $charset);
+	
+	if (!$sys_sendmail_path){
+		$sys_sendmail_path="/usr/sbin/sendmail";
+	}
+
+	exec ("/bin/echo ". util_prep_string_for_sendmail($body2) .
+		  " | ".$sys_sendmail_path." -f'$from' -t -i > /dev/null 2>&1 &");	
+// WARNING : popen commented code probably brought some trouble, we will use the pipe method as we were before		  
+/*       if (!$handle = popen($sys_sendmail_path." -f'$from' -t -i", "w")) {
+               echo "<p>Error: cannot run '$sys_sendmail_path' - mail not sent</p>\n";
+       } else {
+               fwrite($handle, util_prep_string_for_sendmail($body2));
+               pclose($handle);
+       }*/
+}
+
+/**
+ * util_encode_mailaddr() - Encode email address to MIME format
+ *
+ * @param		string	The email address
+ * @param		string	The email's owner name
+ * @param		string	The converting charset
+ *
+ */
+function util_encode_mailaddr($email,$name,$charset) {
+	if (function_exists('mb_convert_encoding') && trim($name) != "") {
+		$name = "=?".$charset."?B?".
+			base64_encode(mb_convert_encoding(
+				$name,$charset,"UTF-8")).
+			"?=";
+	}
+	
+	return $name." <".$email."> ";
+}
+
+/**
+ * util_encode_mimeheader() - Encode mimeheader
+ *
+ * @param		string	The email subject
+ * @param		string	The converting charset (like ISO-2022-JP)
+ * @return		string	The MIME encoded subject
+ *
+ */
+function util_encode_mimeheader($str,$charset) {
+	if (!function_exists('mb_convert_encoding')) {
+		return $str;
+	}
+
+	return "=?".$charset."?B?".
+		base64_encode(mb_convert_encoding(
+			$str,$charset,"UTF-8")).
+		"?=";
+}
+
+/**
+ * util_convert_body() - Convert body of the email message
+ *
+ * @param		string	The body of the email message
+ * @param		string	The charset of the email message
+ * @return		string	The converted body of the email message
+ *
+ */
+function util_convert_body($str,$charset) {
+	if (!function_exists('mb_convert_encoding') || $charset == 'UTF-8') {
+		return $str;
+	}
+	
+	return mb_convert_encoding($str,$charset,"UTF-8");
+}
+
+function util_send_jabber($to,$subject,$body) {
+	if (!$GLOBALS['sys_use_jabber']) {
+		return;
+	}
+	$JABBER = new Jabber();
+	if (!$JABBER->Connect()) {
+		echo '<br />Unable to connect';
+		return false;
+	}
+	//$JABBER->SendAuth();
+	//$JABBER->AccountRegistration();
+	if (!$JABBER->SendAuth()) {
+		echo '<br />Auth Failure';
+		$JABBER->Disconnect();
+		return false;
+		//or die("Couldn't authenticate!");
+	}
+	$JABBER->SendPresence(NULL, NULL, "online");
+
+	$body=htmlspecialchars($body);
+	$to_arr=explode(',',$to);
+	for ($i=0; $i<count($to_arr); $i++) {
+		if ($to_arr[$i]) {
+			//echo '<br />Sending Jabbers To: '.$to_arr[$i];
+			if (!$JABBER->SendMessage($to_arr[$i], "normal", NULL, array("body" => $body,"subject"=>$subject))) {
+				echo '<br />Error Sending to '.$to_arr[$i];
+			}
+		}
+	}
+
+	$JABBER->CruiseControl(2);
+	$JABBER->Disconnect();
+}
+
+/**
+ * util_prep_string_for_sendmail() - Prepares a string to be sent by email
+ *
+ * @param		string	The text to be prepared
+ * @returns The prepared text
+ *
+ */
+function util_prep_string_for_sendmail($body) {
+	/*$body=str_replace("`","\\`",$body);
+	$body=str_replace("\"","\\\"",$body);
+	$body=str_replace("\$","\\\$",$body);*/
+	$body = escapeshellarg($body);
+	return $body;
+}
+
+/**
+ *	util_handle_message() - a convenience wrapper which sends messages
+ *	to either a jabber account or email account or both, depending on
+ *	user preferences
+ *
+ *	@param	array	array of user_id's from the user table
+ *	@param	string	subject of the message
+ *	@param	string	the message body
+ *	@param	string	a comma-separated list of email address
+ *	@param	string	a comma-separated list of jabber address
+ *	@param	string	From header
+ */
+function util_handle_message($id_arr,$subject,$body,$extra_emails='',$extra_jabbers='',$from='') {
+	$address=array();
+
+	if (count($id_arr) < 1) {
+
+	} else {
+		$res=db_query("SELECT user_id, jabber_address,email,jabber_only
+			FROM users WHERE user_id IN (". implode($id_arr,',') .")");
+		$rows=db_numrows($res);
+
+		for ($i=0; $i<$rows; $i++) {
+			if (db_result($res, $i, 'user_id') == 100) {
+				// Do not send messages to "Nobody"
+				continue;
+			}
+			//
+			//  Build arrays of the jabber address
+			//
+			if (db_result($res,$i,'jabber_address')) {
+				$address['jabber_address'][]=db_result($res,$i,'jabber_address');
+				if (db_result($res,$i,'jabber_only') != 1) {
+					$address['email'][]=db_result($res,$i,'email');
+				}
+			} else {
+				$address['email'][]=db_result($res,$i,'email');
+			}
+		}
+		if (count($address['email']) > 0) {
+			$extra_email1=implode($address['email'],',').',';
+		}
+		if (count($address['jabber_address']) > 0) {
+			$extra_jabber1=implode($address['jabber_address'],',').',';
+		}
+	}
+	if ($extra_email1 || $extra_emails) {
+		util_send_message('',$subject,$body,$from,$extra_email1.$extra_emails);
+	}
+	if ($extra_jabber1 || $extra_jabbers) {
+		util_send_jabber($extra_jabber1.$extra_jabbers,$subject,$body);
+	}
+}
+
+/**
+ * util_unconvert_htmlspecialchars() - Unconverts a string converted with htmlspecialchars()
+ * This function requires PHP 4.0.3 or greater
+ *
+ * @param		string	The string to unconvert
+ * @returns The unconverted string
+ *
+ */
+function util_unconvert_htmlspecialchars($string) {
+	if (strlen($string) < 1) {
+		return '';
+	} else {
+		//$trans = get_html_translation_table(HTMLENTITIES, ENT_QUOTES);
+		$trans = get_html_translation_table(HTML_ENTITIES);
+		$trans = array_flip ($trans);
+		$str = strtr ($string, $trans);
+		return $str;
+	}
+}
+
+/**
+ * util_result_columns_to_assoc() - Takes a result set and turns the column pair into an associative array
+ *
+ * @param		string	The result set ID
+ * @param		int		The column key
+ * @param		int		The optional column value
+ * @returns An associative array
+ *
+ */
+function util_result_columns_to_assoc($result, $col_key=0, $col_val=1) {
+	$rows=db_numrows($result);
+
+	if ($rows > 0) {
+		$arr=array();
+		for ($i=0; $i<$rows; $i++) {
+			$arr[db_result($result,$i,$col_key)]=db_result($result,$i,$col_val);
+		}
+	} else {
+		$arr=array();
+	}
+	return $arr;
+}
+
+/**
+ * util_result_column_to_array() - Takes a result set and turns the optional column into an array
+ *
+ * @param		int		The result set ID
+ * @param		int		The column
+ * @resturns An array
+ *
+ */
+function &util_result_column_to_array($result, $col=0) {
+	/*
+		Takes a result set and turns the optional column into
+		an array
+	*/
+	$rows=db_numrows($result);
+
+	if ($rows > 0) {
+		$arr=array();
+		for ($i=0; $i<$rows; $i++) {
+			$arr[$i]=db_result($result,$i,$col);
+		}
+	} else {
+		$arr=array();
+	}
+	return $arr;
+}
+
+/**
+ * util_wrap_find_space() - Find the first space in a string
+ *
+ * @param		string	The string in which to find the space (must be UTF8!)
+ * @param		int		The number of characters to wrap - Default is 80
+ * @returns The position of the first space
+ *
+ */
+function util_wrap_find_space($string,$wrap) {
+	//echo"\n";
+	$start=$wrap-5;
+	$try=1;
+	$found=false;
+
+	while (!$found) {
+		//find the first space starting at $start
+		$pos=@strpos($string,' ',$start);
+
+		//if that space is too far over, go back and start more to the left
+		if (($pos > ($wrap+5)) || !$pos) {
+			$try++;
+			$start=($wrap-($try*5));
+			//if we've gotten so far left , just truncate the line
+			if ($start<=20) {
+				while ($wrap >= 1) {
+					$code = ord(substr($string,$wrap,1));
+					if ($code <= 0x7F ||
+					    $code >= 0xC0) {
+						//Here is single byte character
+						//or head of multi byte character  
+						return $wrap;
+					}
+					//Do not break multi byte character
+					$wrap--;
+				}
+				return $wrap;
+			}
+			$found=false;
+		} else {
+			$found=true;
+		}
+	}
+
+	return $pos;
+}
+
+/**
+ * util_line_wrap() - Automatically linewrap text
+ *
+ * @param		string	The text to wrap
+ * @param		int		The number of characters to wrap - Default is 80
+ * @param		string	The line break to use - Default is '\n'
+ * @returns The wrapped text
+ *
+ */
+function util_line_wrap ($text, $wrap = 80, $break = "\n") {
+	$paras = explode("\n", $text);
+
+	$result = array();
+	$i = 0;
+	while ($i < count($paras)) {
+		if (strlen($paras[$i]) <= $wrap) {
+			$result[] = $paras[$i];
+			$i++;
+		} else {
+			$pos=util_wrap_find_space($paras[$i],$wrap);
+
+			$result[] = substr($paras[$i], 0, $pos);
+
+			$new = trim(substr($paras[$i], $pos, strlen($paras[$i]) - $pos));
+			if ($new != '') {
+				$paras[$i] = $new;
+				$pos=util_wrap_find_space($paras[$i],$wrap);
+			} else {
+				$i++;
+			}
+		}
+	}
+	return implode($break, $result);
+}
+
+/**
+ * util_make_links() - Turn URL's into HREF's.
+ *
+ * @param		string	The URL
+ * @returns The HREF'ed URL
+ *
+ */
+function util_make_links ($data='') {
+	if(empty($data)) { 
+		return $data; 
+	}
+	$lines = split("\n",$data);
+	$newText = "";
+	while ( list ($key,$line) = each ($lines)) {
+		// When we come here, we usually have form input
+		// encoded in entities. Our aim is to NOT include
+		// angle brackets in the URL
+		// (RFC2396; http://www.w3.org/Addressing/URL/5.1_Wrappers.html)
+		$line = str_replace('&gt;', "\1", $line);
+		$line = eregi_replace("([ \t]|^)www\."," http://www.",$line);
+		$text = eregi_replace("([[:alnum:]]+)://([^[:space:]<\1]*)([[:alnum:]#?/&=])", "<a href=\"\\1://\\2\\3\" target=\"_new\">\\1://\\2\\3</a>", $line);
+		$text = eregi_replace("([[:space:]]|^)(([a-z0-9_]|\\-|\\.)+@([^[:space:]]*)([[:alnum:]-]))", "\\1<a href=\"mailto:\\2\" target=\"_new\">\\2</a>", $text);
+		$text = str_replace("\1", '&gt;', $text);
+		$newText .= $text;
+	}
+	return $newText;
+}
+
+/**
+ * show_priority_colors_key() - Show the priority colors legend
+ *
+ */
+function show_priority_colors_key() {
+	global $Language;
+	echo '<p /><strong> '.$Language->getText('common_utils','priority_colors').':</strong><br />
+
+		<table border="0"><tr>';
+
+	for ($i=1; $i<6; $i++) {
+		echo '
+			<td bgcolor="'.html_get_priority_color($i).'">'.$i.'</td>';
+	}
+	echo '</tr></table>';
+}
+
+/**
+ * utils_buildcheckboxarray() - Build a checkbox array
+ *
+ * @param		int		Number of options to be in the array
+ * @param		string	The name of the checkboxes
+ * @param		array	An array of boxes to be pre-checked
+ *
+ */
+function utils_buildcheckboxarray($options,$name,$checked_array) {
+	$option_count=count($options);
+	$checked_count=count($checked_array);
+
+	for ($i=1; $i<=$option_count; $i++) {
+		echo '
+			<br /><input type="checkbox" name="'.$name.'" value="'.$i.'"';
+		for ($j=0; $j<$checked_count; $j++) {
+			if ($i == $checked_array[$j]) {
+				echo ' CHECKED';
+			}
+		}
+		echo '> '.$options[$i];
+	}
+}
+
+/**
+ * utils_requiredField() - Adds the required field marker
+ *
+ * @return	a string holding the HTML to mark a required field
+ */
+function utils_requiredField() {
+	return '<span><font color="red">*</font></span>';
+}
+
+/**
+ * GraphResult() - Takes a database result set and builds a graph.
+ * The first column should be the name, and the second column should be the values
+ * Be sure to include HTL_Graphs.php before using this function
+ *
+ * @author Tim Perdue tperdue at valinux.com
+ * @param		int		The databse result set ID
+ * @param		string	The title of the graph
+ *
+ */
+Function GraphResult($result,$title) {
+	$rows=db_numrows($result);
+
+	if ((!$result) || ($rows < 1)) {
+		echo 'None Found.';
+	} else {
+		$names=array();
+		$values=array();
+
+		for ($j=0; $j<db_numrows($result); $j++) {
+			if (db_result($result, $j, 0) != '' && db_result($result, $j, 1) != '' ) {
+				$names[$j]= db_result($result, $j, 0);
+				$values[$j]= db_result($result, $j, 1);
+			}
+		}
+
+	/*
+		This is another function detailed below
+	*/
+		GraphIt($names,$values,$title);
+	}
+}
+
+/**
+ * GraphIt() - Build a graph
+ *
+ * @author Tim Perdue tperdue at valinux.com
+ * @param		array	An array of names
+ * @param		array	An array of values
+ * @param		string	The title of the graph
+ *
+ */
+Function GraphIt($name_string,$value_string,$title) {
+	GLOBAL $HTML;
+
+	$counter=count($name_string);
+
+	/*
+		Can choose any color you wish
+	*/
+	$bars=array();
+
+	for ($i = 0; $i < $counter; $i++) {
+		$bars[$i]=$HTML->COLOR_LTBACK1;
+	}
+
+	$counter=count($value_string);
+
+	/*
+		Figure the max_value passed in, so scale can be determined
+	*/
+
+	$max_value=0;
+
+	for ($i = 0; $i < $counter; $i++) {
+		if ($value_string[$i] > $max_value) {
+			$max_value=$value_string[$i];
+		}
+	}
+
+	if ($max_value < 1) {
+		$max_value=1;
+	}
+
+	/*
+		I want my graphs all to be 800 pixels wide, so that is my divisor
+	*/
+
+	$scale=(400/$max_value);
+
+	/*
+		I create a wrapper table around the graph that holds the title
+	*/
+
+	$title_arr=array();
+	$title_arr[]=$title;
+
+	echo $GLOBALS['HTML']->listTableTop ($title_arr);
+	echo '<tr><td>';
+	/*
+		Create an associate array to pass in. I leave most of it blank
+	*/
+
+	$vals =  array(
+	'vlabel'=>'',
+	'hlabel'=>'',
+	'type'=>'',
+	'cellpadding'=>'',
+	'cellspacing'=>'0',
+	'border'=>'',
+	'width'=>'',
+	'background'=>'',
+	'vfcolor'=>'',
+	'hfcolor'=>'',
+	'vbgcolor'=>'',
+	'hbgcolor'=>'',
+	'vfstyle'=>'',
+	'hfstyle'=>'',
+	'noshowvals'=>'',
+	'scale'=>$scale,
+	'namebgcolor'=>'',
+	'valuebgcolor'=>'',
+	'namefcolor'=>'',
+	'valuefcolor'=>'',
+	'namefstyle'=>'',
+	'valuefstyle'=>'',
+	'doublefcolor'=>'');
+
+	/*
+		This is the actual call to the HTML_Graphs class
+	*/
+
+	html_graph($name_string,$value_string,$bars,$vals);
+
+	echo '
+		</td></tr>
+		<!-- end outer graph table -->';
+	echo $GLOBALS['HTML']->listTableBottom();
+}
+
+/**
+ * ShowResultSet() - Show a generic result set
+ * Very simple, plain way to show a generic result set
+ *
+ * @param	int		The result set ID
+ * @param	string	The title of the result set
+ * @param	bool	The option to turn URL's into links
+ * @param	bool	The option to display headers
+ * @param	array	The db field name -> label mapping
+ * @param	array   Don't display these cols
+ *
+ */
+function ShowResultSet($result,$title='',$linkify=false,$displayHeaders=true,$headerMapping=array(), $excludedCols=array())  {
+	global $group_id,$HTML;
+
+	if($result)  {
+		$rows  =  db_numrows($result);
+		$cols  =  db_numfields($result);
+
+		echo '<table border="0" width="100%">';
+
+		/*  Create  the  headers  */
+		$headersCellData = array();
+		$colsToKeep = array();
+		for ($i=0; $i < $cols; $i++) {
+			$fieldName = db_fieldname($result, $i);
+			if(in_array($fieldName, $excludedCols)) {
+				continue;
+			}
+			$colsToKeep[] = $i;
+			if(isset($headerMapping[$fieldName])) {
+				if(is_array($headerMapping[$fieldName])) {
+					$headersCellData[] = $headerMapping[$fieldName];
+				} else {
+					$headersCellData[] = array($headerMapping[$fieldName]);
+				}
+			}
+			else {
+				$headersCellData[] = array($fieldName);
+			}
+		}
+		
+		/*  Create the title  */
+		if(strlen($title) > 0) {
+			$titleCellData = array();
+			$titleCellData[] = array($title, 'colspan="'.count($headersCellData).'"');
+			echo $HTML->multiTableRow('', $titleCellData, TRUE);
+		}
+		
+		/* Display the headers */
+		if($displayHeaders) {
+			echo $HTML->multiTableRow('', $headersCellData, TRUE);
+		}
+
+		/*  Create the rows  */
+ 		for ($j = 0; $j < $rows; $j++) {
+			echo '<tr '. $HTML->boxGetAltRowStyle($j) . '>';
+			for ($i = 0; $i < $cols; $i++) {
+				if(in_array($i, $colsToKeep)) {
+					if ($linkify && $i == 0) {
+						$link = '<a href="'.$PHP_SELF.'?';
+						$linkend = '</a>';
+						if ($linkify == "bug_cat") {
+							$link .= 'group_id='.$group_id.'&amp;bug_cat_mod=y&amp;bug_cat_id='.db_result($result, $j, 'bug_category_id').'">';
+						} else if($linkify == "bug_group") {
+							$link .= 'group_id='.$group_id.'&amp;bug_group_mod=y&amp;bug_group_id='.db_result($result, $j, 'bug_group_id').'">';
+						} else if($linkify == "patch_cat") {
+							$link .= 'group_id='.$group_id.'&amp;patch_cat_mod=y&amp;patch_cat_id='.db_result($result, $j, 'patch_category_id').'">';
+						} else if($linkify == "support_cat") {
+							$link .= 'group_id='.$group_id.'&amp;support_cat_mod=y&amp;support_cat_id='.db_result($result, $j, 'support_category_id').'">';
+						} else if($linkify == "pm_project") {
+							$link .= 'group_id='.$group_id.'&amp;project_cat_mod=y&amp;project_cat_id='.db_result($result, $j, 'group_project_id').'">';
+						} else {
+							$link = $linkend = '';
+						}
+					} else {
+						$link = $linkend = '';
+					}
+					echo '<td>'.$link . db_result($result,  $j,  $i) . $linkend.'</td>';
+				}
+			}
+			echo '</tr>';
+		}
+		echo '</table>';
+	} else {
+		echo db_error();
+	}
+}
+
+/**
+ * validate_email() - Validate an email address
+ *
+ * @param		string	The address string to validate
+ * @returns true on success/false on error
+ *
+ */
+function validate_email ($address) {
+	return (ereg('^[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+'. '@'. '[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.' . '[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$', $address));
+}
+
+/**
+ * validate_emails() - Validate a list of e-mail addresses
+ *
+ * @param	string	E-mail list
+ * @param	char	Separator
+ * @returns	array	Array of invalid e-mail addresses (if empty, all addresses are OK)
+*/
+function validate_emails ($addresses, $separator=',') {
+	if (strlen($addresses) == 0) return array();
+	
+	$emails = explode($separator, $addresses);
+	$ret 	= array();
+	
+	if (is_array($emails)) {
+		foreach ($emails as $email) {
+			$email = trim($email);		// This is done so we can validate lists like "a at b.com, c at d.com"
+			if (!validate_email($email)) $ret[] = $email;
+		}
+	}
+	return $ret;
+}
+
+
+
+/**
+ * util_is_valid_filename() - Verifies whether a file has a valid filename
+ *
+ * @param		string	The file to verify
+ * @returns true on success/false on error
+ *
+ */
+function util_is_valid_filename ($file) {
+	//bad char test
+	$invalidchars = eregi_replace("[-A-Z0-9_\.]","",$file);
+
+	if (!empty($invalidchars)) {
+		return false;
+	} else {
+		if (strstr($file,'..')) {
+			return false;
+		} else {
+			return true;
+		}
+	}
+}
+
+/**
+ * valid_hostname() - Validates a hostname string to make sure it doesn't contain invalid characters
+ *
+ * @param		string	The optional hostname string
+ * @returns true on success/false on failur
+ *
+ */
+function valid_hostname ($hostname = "xyz") {
+
+	//bad char test
+	$invalidchars = eregi_replace("[-A-Z0-9\.]","",$hostname);
+
+	if (!empty($invalidchars)) {
+		return false;
+	}
+
+	//double dot, starts with a . or -
+	if (ereg("\.\.",$hostname) || ereg("^\.",$hostname) || ereg("^\-",$hostname)) {
+		return false;
+	}
+
+	$multipoint = explode(".",$hostname);
+
+	if (!(is_array($multipoint)) || ((count($multipoint) - 1) < 1)) {
+		return false;
+	}
+
+	return true;
+
+}
+
+
+/**
+ * human_readable_bytes() - Translates an integer representing bytes to a human-readable format.
+ *
+ * Format file size in a human-readable way
+ * such as "xx Megabytes" or "xx Mo"
+ *
+ * @author           Andrea Paleni <andreaSPAMLESS_AT_SPAMLESScriticalbit.com>
+ * @version        1.0
+ * @param int       bytes   is the size
+ * @param bool     base10  enable base 10 representation, otherwise
+ *                 default base 2  is used  
+ * @param int       round   number of fractional digits
+ * @param array     labels  strings associated to each 2^10 or
+ *                  10^3(base10==true) multiple of base units
+ */
+function human_readable_bytes ($bytes, $base10=false, $round=0, $labels=array(' bytes',  ' KB', ' MB', ' GB')) {
+	if ($bytes <= 0 || !is_array($labels) || (count($labels) <= 0)) {
+		return null;
+	}
+	$step = $base10 ? 3 : 10;
+	$base = $base10 ? 10 : 2;
+	$log = (int)(log10($bytes)/log10($base));
+	krsort($labels);
+	foreach ($labels as $p=>$lab) {
+		$pow = $p * $step;
+		if ($log < $pow) {
+			continue;
+		}
+		if ($lab == " MB") {
+			$round = 2;
+		}
+		$text = round($bytes/pow($base,$pow),$round).$lab;
+		break;
+	}
+	return $text;
+}
+
+/**
+ *	ls - lists a specified directory and returns an array of files
+ *	@param	string	the path of the directory to list
+ *	@param	boolean	whether to filter out directories and illegal filenames
+ *	@return	array	array of file names.
+ */
+function &ls($dir,$filter=false) {
+	exec('ls -c1 '.$dir,$out);
+	if ($filter) {
+		for ($i=0; $i<count($out); $i++) {
+			if (util_is_valid_filename($out[$i]) && is_file($dir.'/'.$out[$i])) {
+				$filtered[]=$out[$i];
+			}
+		}
+		return $filtered;
+	} else {
+		return $out;
+	}
+}
+
+/**
+ * readfile_chunked() - replacement for readfile
+ *
+ * @param		string	The file path
+ * @param		bool    Whether to return bytes served or just a bool
+ *
+ */
+function readfile_chunked($filename, $returnBytes=true) {
+    $chunksize = 1*(1024*1024); // 1MB chunks
+    $buffer = '';
+    $byteCounter = 0;
+    
+    $handle = fopen($filename, 'rb');
+    if ($handle === false) {
+        return false;
+    }
+    
+    while (!feof($handle)) {
+        $buffer = fread($handle, $chunksize);
+        echo $buffer;
+        if ($returnBytes) {
+            $byteCounter += strlen($buffer);
+		}
+    }
+    $status = fclose($handle);
+    if ($returnBytes && $status) {
+        return $byteCounter; // return num. bytes delivered like readfile() does.
+    }
+    return $status;
+}
+
+/**
+ * util_is_root_dir() - Checks if a directory points to the root dir
+ * @param	string	Directory
+ * @return bool
+ */
+function util_is_root_dir($dir) {
+	return !preg_match('/[^\\/]/',$dir);
+}
+?>
\ No newline at end of file

Added: trunk/gforge_base/gforge/common/mail/MailingList.class
===================================================================
--- trunk/gforge_base/gforge/common/mail/MailingList.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/mail/MailingList.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,400 @@
+<?php
+/**
+ * GForge Mailing Lists Facility
+ *
+ * Copyright 2003 Guillaume Smet
+ * http://gforge.org/
+ *
+ * @version   $Id: MailingList.class 5378 2006-03-20 13:08:53Z danper $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+ /*
+ 
+ This work is based on Tim Perdue's work on the forum stuff
+ 
+ */
+
+require_once('common/include/Error.class');
+
+class MailingList extends Error {
+
+	/**
+	 * Associative array of data from db.
+	 *
+	 * @var	 array   $dataArray.
+	 */
+	var $dataArray;
+
+	/**
+	 * The Group object.
+	 *
+	 * @var	 object  $Group.
+	 */
+	var $Group;
+	
+	/**
+	 * The mailing list id
+	 *
+	 * @var int $groupMailingListId
+	 */
+	var $groupMailingListId;
+
+	/**
+	 *  Constructor.
+	 *
+	 * @param	object	The Group object to which this mailing list is associated.
+	 * @param	int		The group_list_id.
+	 * @param	array		The associative array of data.
+	 * @return	boolean	success.
+	 */
+	function MailingList(&$Group, $groupListId = false, $dataArray = false) {
+		global $Language;
+		$this->Error();
+		if (!$Group || !is_object($Group)) {
+			$this->setError($Language->getText('general', 'error_no_valid_group_object', array('MailingList')));
+			return false;
+		}
+		if ($Group->isError()) {
+			$this->setError('MailingList:: '.$Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $Group;
+
+		if ($groupListId) {
+			$this->groupMailingListId = $groupListId;
+			if (!$dataArray || !is_array($dataArray)) {
+				if (!$this->fetchData($groupListId)) {
+					return false;
+				}
+			} else {
+				$this->dataArray =& $dataArray;
+				if ($this->dataArray['group_id'] != $this->Group->getID()) {
+					$this->setError($Language->getText('general', 'error_group_id'));
+					$this->dataArray = null;
+					return false;
+				}
+			}
+			if (!$this->isPublic()) {
+				$perm =& $this->Group->getPermission(session_get_user());
+
+				if (!$perm || !is_object($perm) || !$perm->isMember()) {
+					$this->setPermissionDeniedError();
+					$this->dataArray = null;
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+
+	/**
+	 *	create - use this function to create a new entry in the database.
+	 *
+	 *	@param	string	The name of the mailing list
+	 *	@param	string	The description of the mailing list
+	 *	@param	int	Pass (1) if it should be public (0) for private.
+	 *
+	 *	@return	boolean	success.
+	 */
+	function create($listName, $description, $isPublic = MAIL__MAILING_LIST_IS_PUBLIC,$creator_id=false) {
+		global $Language;
+		
+		//
+		//	During the group creation, the current user_id will not match the admin's id
+		//
+		if (!$creator_id) {
+			$creator_id=user_getid();
+			if(!$this->userIsAdmin()) {
+				$this->setPermissionDeniedError();
+				return false;
+			}
+		}
+		
+		if(!$listName || strlen($listName) < MAIL__MAILING_LIST_NAME_MIN_LENGTH) {
+			$this->setError($Language->getText('mail_common', 'error_min_name_length'));
+			return false;
+		}
+		
+		$realListName = strtolower($this->Group->getUnixName().'-'.$listName);
+		
+		if(!validate_email($realListName.'@'.$GLOBALS['sys_lists_host'])) {
+			$this->setError($Language->getText('mail_common', 'error_invalid_name') . ': ' .
+			$realListName.'@'.$GLOBALS['sys_lists_host']);
+			return false;
+		}
+
+		$result = db_query('SELECT 1 FROM mail_group_list WHERE lower(list_name)=\''.$realListName.'\'');
+
+		if (db_numrows($result) > 0) {
+			$this->setError($Language->getText('mail_common', 'error_list_already_exists'));
+			return false;
+		}
+			
+		$listPassword = substr(md5($GLOBALS['session_hash'] . time() . rand(0,40000)), 0, 16);
+		
+		$sql = 'INSERT INTO mail_group_list '
+			. '(group_id, list_name, is_public, password, list_admin, status, description) VALUES ('
+			. $this->Group->getID(). ', '
+			. "'".$realListName."',"
+			. "'".$isPublic."',"
+			. "'".$listPassword."',"
+			. "'".$creator_id."',"
+			. "'".MAIL__MAILING_LIST_IS_REQUESTED."',"
+			. "'".$description."')";
+		
+		db_begin();
+		$result = db_query($sql);
+		
+		if (!$result) {
+			db_rollback();
+			$this->setError($Language->getText('general', 'error_creating', array($Language->getText('mail_common', 'mailing_list'))).db_error());
+			return false;
+		}
+			
+		$this->groupMailingListId = db_insertid($result, 'mail_group_list', 'group_list_id');
+		$this->fetchData($this->groupMailingListId);
+		
+		$user = &user_get_object($creator_id);
+		$userEmail = $user->getEmail();
+		if(empty($userEmail) || !validate_email($userEmail)) {
+			db_rollback();
+			$this->setInvalidEmailError();
+			return false;
+		} else {
+			$mailBody = stripcslashes($Language->getText('mail_common', 'creation_mail_body', array($GLOBALS['sys_name'], $GLOBALS['sys_lists_host'], $realListName, $this->getExternalInfoUrl(), $this->getExternalAdminUrl(), $listPassword)));
+			$mailSubject = $Language->getText('mail_common', 'creation_mail_subject', array($GLOBALS['sys_name']));
+			
+			util_send_message($userEmail, $mailSubject, $mailBody, 'admin@'.$GLOBALS['sys_default_domain']);
+		}
+		
+		db_commit();
+		return true;
+	}
+
+	/**
+	 *  fetchData - re-fetch the data for this mailing list from the database.
+	 *
+	 *  @param  int	 The list_id.
+	 *	@return	boolean	success.
+	 */
+	function fetchData($groupListId) {
+		global $Language;
+		$res=db_query("SELECT * FROM mail_group_list "
+			. "WHERE group_list_id='".$groupListId."' "
+			. "AND group_id='". $this->Group->getID() ."'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError($Language->getText('general', 'error_getting', array($Language->getText('mail_common', 'mailing_list'))));
+			return false;
+		}
+		$this->dataArray =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	update - use this function to update an entry in the database.
+	 *
+	 *	@param	string	The description of the mailing list
+	 *	@param	int	Pass (1) if it should be public (0) for private
+	 *	@return	boolean	success.
+	 */
+	function update($description, $isPublic = MAIL__MAILING_LIST_IS_PUBLIC) {
+		global $Language;
+		
+		if(! $this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		
+		$sql = "UPDATE mail_group_list 
+			SET is_public='".$isPublic."', 
+			description='". $description ."' 
+			WHERE group_list_id='".$this->groupMailingListId."' 
+			AND group_id='".$this->Group->getID()."'";
+			
+		$res = db_query($sql);
+
+		if (!$res || db_affected_rows($res) < 1) {
+			$this->setError($Language->getText('general', 'error_on_update').db_error());
+			return false;
+		}
+		return true;
+	}
+
+	/**
+	 *	getGroup - get the Group object this mailing list is associated with.
+	 *
+	 *	@return	object	The Group object.
+	 */
+	function &getGroup() {
+		return $this->Group;
+	}
+
+	/**
+	 *	getID - The id of this mailing list
+	 *
+	 *	@return	int	The group_list_id #.
+	 */
+	function getID() {
+		return $this->dataArray['group_list_id'];
+	}
+
+
+	/**
+	 *	isPublic - Is this mailing list open to the general public.
+	 *
+	 *	@return boolean	is_public.
+	 */
+	function isPublic() {
+		return $this->dataArray['is_public'];
+	}
+
+	/**
+	 *	getName - get the name of this mailing list
+	 *
+	 *	@return string	The name of this mailing list
+	 */
+	function getName() {
+		return $this->dataArray['list_name'];
+	}
+
+
+	/**
+	 *	getDescription - get the description of this mailing list
+	 *
+	 *	@return string	The description.
+	 */
+	function getDescription() {
+		return $this->dataArray['description'];
+	}
+	
+	/**
+	 * getPassword - get the password to administrate the mailing list
+	 *
+	 * @return string The password
+	 */
+	function getPassword() {
+		return $this->dataArray['password'];
+	}
+	
+	/**
+	 * getListAdmin - get the user who is the admin of this mailing list
+	 *
+	 * @return User The admin user
+	 */
+	function getListAdmin() {
+		return user_get_object($this->dataArray['list_admin']);
+	}
+	
+	/**
+	 * getStatus - get the status of this mailing list
+	 *
+	 * @return int The status
+	 */
+	function getStatus() {
+		return $this->dataArray['status'];
+	}
+	
+	/**
+	 * getArchivesUrl - get the url to see the archives of the list
+	 *
+	 * @return string url of the archives
+	 */
+	function getArchivesUrl() {
+		if ($this->isPublic()) {
+			return 'http://'.$GLOBALS['sys_lists_host'].'/pipermail/'.$this->getName().'/';
+		} else {
+			return 'http://'.$GLOBALS['sys_lists_host'].'/cgi-bin/mailman/private/'.$this->getName().'/';
+		}
+	}
+	
+	/**
+	 * getExternalInfoUrl - get the url to subscribe/unsubscribe
+	 *
+	 * @return string url of the info page
+	 */
+	function getExternalInfoUrl() {
+		return 'http://'.$GLOBALS['sys_lists_host'].'/cgi-bin/mailman/listinfo/'.$this->getName();
+	}
+	
+	/**
+	 * getExternalAdminUrl - get the url to admin the list with the external tools used
+	 *
+	 * @return string url of the admin
+	 */
+	function getExternalAdminUrl() {
+		return 'http://'.$GLOBALS['sys_lists_host'].'/cgi-bin/mailman/admin/'.$this->getName();
+	}
+
+	/**
+	 *	delete - permanently delete this mailing list
+	 *
+	 *	@param	boolean	I'm Sure.
+	 *	@param	boolean	I'm Really Sure.
+	 *	@return	boolean success;
+	 */
+	function delete($sure,$really_sure) {
+
+		if (!$sure || !$really_sure) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		$sql="INSERT INTO deleted_mailing_lists (mailing_list_name,
+			delete_date,isdeleted) VALUES ('".$this->getName()."','".time()."','0')";
+		$res=db_query($sql);
+		if (!$res) {
+			$this->setError('Could Not Insert Into Delete Queue: '.db_error());
+			return false;
+		}
+		$res=db_query("DELETE FROM mail_group_list WHERE 
+			group_list_id='".$this->getID()."'");
+		if (!$res) {
+			$this->setError('Could Not Delete List: '.db_error());
+			return false;
+		}
+		return true;
+		
+	}
+
+	/**
+	 * userIsAdmin - use this function to know if the user can administrate mailing lists
+	 *
+	 * This is a static method. Currently the user must be a project or a sitewide admin to administrate the mailing lists
+	 *
+	 * @return boolean true if the user can administrate mailing lists
+	 */
+	function userIsAdmin() {
+		$perm = & $this->Group->getPermission(session_get_user());
+		if (!$perm || !is_object($perm)) {
+			return false;
+		} elseif ($perm->isAdmin()) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/mail/MailingListFactory.class
===================================================================
--- trunk/gforge_base/gforge/common/mail/MailingListFactory.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/mail/MailingListFactory.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,125 @@
+<?php
+/**
+ * GForge Mailing Lists Facility
+ *
+ * Copyright 2003 Guillaume Smet
+ * http://gforge.org/
+ *
+ * @version   $Id: MailingListFactory.class 4167 2005-03-15 03:35:39Z tperdue $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+ 
+ /*
+ 
+ This work is based on Tim Perdue's work on the forum stuff
+ 
+ */
+
+require_once('common/include/Error.class');
+require_once('common/mail/MailingList.class');
+
+class MailingListFactory extends Error {
+
+	/**
+	 * The Group object.
+	 *
+	 * @var	 object  $Group.
+	 */
+	var $Group;
+
+	/**
+	 * The mailing lists array.
+	 *
+	 * @var	 array	$mailingLists.
+	 */
+	var $mailingLists;
+
+
+	/**
+	 *  Constructor.
+	 *
+	 *	@param	object	The Group object to which these mailing lists are associated.
+	 */
+	function MailingListFactory(& $Group) {
+		global $Language;
+		$this->Error();
+		
+		if (!$Group || !is_object($Group)) {
+			$this->setError($Language->getText('general', 'error_no_valid_group_object', array('MailingListFactory')));
+			return false;
+		}
+		if ($Group->isError()) {
+			$this->setError('MailingListFactory:: '.$Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $Group;
+
+		return true;
+	}
+
+	/**
+	 *	getGroup - get the Group object this MailingListFactory is associated with.
+	 *
+	 *	@return object	The Group object.
+	 */
+	function &getGroup() {
+		return $this->Group;
+	}
+
+	/**
+	 *	getMailingLists - get an array of MailingList objects for this Group.
+	 *
+	 * @param boolean $admin if we are in admin mode (we want to see deleted lists)
+	 *	@return	array	The array of MailingList objects.
+	 */
+	function &getMailingLists() {
+		global $Language;
+		if (isset($this->mailingLists) && is_array($this->mailingLists)) {
+			return $this->mailingLists;
+		}
+		
+		$public_flag = MAIL__MAILING_LIST_IS_PUBLIC;
+		
+		$perm = & $this->Group->getPermission(session_get_user());
+		if ($perm && is_object($perm) && $perm->isMember()) {
+			$public_flag = MAIL__MAILING_LIST_IS_PRIVATE.', '.MAIL__MAILING_LIST_IS_PUBLIC;
+		}
+
+		$sql = 'SELECT * '
+			. 'FROM mail_group_list '
+			. 'WHERE group_id=\''.$this->Group->getID().'\' '
+			. 'AND is_public IN ('.$public_flag.') '
+			. 'ORDER BY list_name;';
+		
+
+		$result = db_query($sql);
+
+		if (!$result) {
+			$this->setError($Language->getText('general', 'error_getting', array($Language->getText('mail_common', 'mailing_list'))).db_error());
+			return false;
+		} else {
+			$this->mailingLists = array();
+			while ($arr = db_fetch_array($result)) {
+				$this->mailingLists[] = new MailingList($this->Group, $arr['group_list_id'], $arr);
+			}
+		}
+		return $this->mailingLists;
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/pm/ProjectCategory.class
===================================================================
--- trunk/gforge_base/gforge/common/pm/ProjectCategory.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/pm/ProjectCategory.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,212 @@
+<?php
+/**
+ * GForge Project Management Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: ProjectCategory.class 1706 2003-02-12 17:23:48Z bigdisk $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+
+/*
+
+	Project/Task Manager
+	By Tim Perdue, Sourceforge, 11/99
+	Heavy rewrite by Tim Perdue April 2000
+
+	Total rewrite in OO and GForge coding guidelines 12/2002 by Tim Perdue
+*/
+
+require_once('common/include/Error.class');
+
+class ProjectCategory extends Error {
+
+	/** 
+	 * The ProjectGroup object.
+	 *
+	 * @var		object	$ProjectGroup.
+	 */
+	var $ProjectGroup; //object
+
+	/**
+	 * Array of data.
+	 *
+	 * @var		array	$data_array.
+	 */
+	var $data_array;
+
+	/**
+	 *  ProjectCategory - constructor.
+	 *
+	 *	@param	object	ProjectGroup object.
+	 *  @param	array	(all fields from project_category) OR category_id from database.
+	 *  @return	boolean	success.
+	 */
+	function ProjectCategory(&$ProjectGroup, $data=false) {
+		$this->Error(); 
+
+		//was ProjectGroup legit?
+		if (!$ProjectGroup || !is_object($ProjectGroup)) {
+			$this->setError('ProjectCategory: No Valid ProjectGroup');
+			return false;
+		}
+		//did ProjectGroup have an error?
+		if ($ProjectGroup->isError()) {
+			$this->setError('ProjectCategory: '.$ProjectGroup->getErrorMessage());
+			return false;
+		}
+		$this->ProjectGroup =& $ProjectGroup;
+
+		if ($data) {
+			if (is_array($data)) {
+				$this->data_array =& $data;
+//
+//	should verify group_project_id
+//
+				return true;
+			} else {
+				if (!$this->fetchData($data)) {
+					return false;
+				} else {
+					return true;
+				}
+			}
+		}
+	}
+
+	/**
+	 *	create - create a new item in the database.
+	 *
+	 *	@param	string	Item name.
+	 *  @return	boolean success.
+	 */
+	function create($name) {
+
+		global $Language;
+		//
+		//	data validation
+		//
+		if (!$name) {
+			$this->setError($Language->getText('pm_projectcategory','required_fields'));
+			return false;
+		}
+
+		$perm =& $this->ProjectGroup->Group->getPermission (session_get_user());
+		if (!$perm || !$perm->isPMAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		$sql="INSERT INTO project_category (group_project_id,category_name) 
+			VALUES ('".$this->ProjectGroup->getID()."','".htmlspecialchars($name)."')";
+
+		$result=db_query($sql);
+
+		if ($result && db_affected_rows($result) > 0) {
+			$this->clearError();
+			return true;
+		} else {
+			$this->setError(db_error());
+			return false;
+		}
+
+/*
+			//
+			//	Now set up our internal data structures
+			//
+			if (!$this->fetchData($id)) {
+				return false;
+			}
+*/
+	}
+
+	/**
+	 *	fetchData() - re-fetch the data for this ProjectCategory from the database.
+	 *
+	 *	@param	int		ID of the category.
+	 *	@return	boolean	success.
+	 */
+	function fetchData($id) {
+		$res=db_query("SELECT * FROM project_category WHERE category_id='$id'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('ProjectCategory: Invalid ProjectCategory ID');
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	getProjectGroup - get the ProjectGroup Object this ProjectCategory is associated with.
+	 *
+	 *	@return	object	ProjectGroup.
+	 */
+	function &getProjectGroup() {
+		return $this->ProjectGroup;
+	}
+	
+	/**
+	 *	getID - get this ProjectCategory's ID.
+	 *
+	 *	@return	int	The id #.
+	 */
+	function getID() {
+		return $this->data_array['category_id'];
+	}
+
+	/**
+	 *	getName - get the name.
+	 *
+	 *	@return	string	The name.
+	 */
+	function getName() {
+		return $this->data_array['category_name'];
+	}
+
+	/**
+	 *  update - update a ProjectCategory.
+	 *
+	 *  @param	string	Name of the category.
+	 *  @return	boolean success.
+	 */
+	function update($name) {
+		$perm =& $this->ProjectGroup->Group->getPermission (session_get_user());
+		if (!$perm || !$perm->isPMAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		if (!$name) {
+			$this->setMissingParamsError();
+			return false;
+		}   
+		$sql="UPDATE project_category 
+			SET category_name='".htmlspecialchars($name)."'
+			WHERE category_id='". $this->getID() ."' 
+			AND group_project_id='".$this->ProjectGroup->getID()."'";
+		$result=db_query($sql);
+		if ($result && db_affected_rows($result) > 0) {
+			return true;
+		} else {
+			$this->setError(db_error());
+			return false;
+		}
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/pm/ProjectGroup.class
===================================================================
--- trunk/gforge_base/gforge/common/pm/ProjectGroup.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/pm/ProjectGroup.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,683 @@
+<?php
+/**
+ * GForge Project Management Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: ProjectGroup.class 5200 2006-01-03 11:29:52Z danper $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+/*
+	Project/Task Manager
+	By Tim Perdue, Sourceforge, 11/99
+	Heavy rewrite by Tim Perdue April 2000
+
+	Total rewrite in OO and GForge coding guidelines 12/2002 by Tim Perdue
+*/
+
+
+require_once('common/include/Error.class');
+
+	/**
+	*	Fetches a ProjectGroup object from the database
+	*
+	* @param group_project_id	the projectgroup id  
+	*	@param data	whether or not the db result handle is passed in
+	*	@return	the ProjectGroup object
+	*/
+	function &projectgroup_get_object($group_project_id,$data=false) {
+		global $PROJECTGROUP_OBJ;
+		if (!isset($PROJECTGROUP_OBJ["_".$group_project_id."_"])) {
+			if ($data) {
+				//the db result handle was passed in
+			} else {
+				$res=db_query("SELECT * FROM project_group_list_vw
+				WHERE group_project_id='$group_project_id'");
+				if (db_numrows($res) <1 ) {
+					$PROJECTGROUP_OBJ["_".$group_project_id."_"]=false;
+					return false;
+				}
+				$data =& db_fetch_array($res);
+			}
+			$Group =& group_get_object($data["group_id"]);
+			$PROJECTGROUP_OBJ["_".$group_project_id."_"]= new ProjectGroup($Group,$group_project_id,$data);
+		}
+		return $PROJECTGROUP_OBJ["_".$group_project_id."_"];
+	}
+
+
+class ProjectGroup extends Error {
+
+	/**
+	 * Associative array of data from db.
+	 *
+	 * @var	 array   $data_array.
+	 */
+	var $data_array;
+
+	/**
+	 * The Group object.
+	 *
+	 * @var	 object  $Group.
+	 */
+	var $Group;
+	var $statuses;
+	var $categories;
+	var $technicians;
+	var $current_user_perm;
+
+	/**
+	 *  Constructor.
+	 *
+	 *	@param	object	The Group object to which this forum is associated.
+	 *  @param  int	 The group_project_id.
+	 *  @param  array	The associative array of data.
+	 *	@return	boolean	success.
+	 */
+	function ProjectGroup(&$Group, $group_project_id=false, $arr=false) {
+		$this->Error();
+		if (!$Group || !is_object($Group)) {
+			$this->setError('ProjectGroup:: No Valid Group Object');
+			return false;
+		}
+		if ($Group->isError()) {
+			$this->setError('ProjectGroup:: '.$Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $Group;
+
+		if ($group_project_id) {
+			if (!$arr || !is_array($arr)) {
+				if (!$this->fetchData($group_project_id)) {
+					return false;
+				}
+			} else {
+				$this->data_array =& $arr;
+				if ($this->data_array['group_id'] != $this->Group->getID()) {
+					$this->setError('Group_id in db result does not match Group Object');
+					return false;
+				}
+			}
+			//
+			//  Make sure they can even access this object
+			//
+			if (!$this->userCanView()) {
+				$this->setPermissionDeniedError();
+				$this->data_array = null;
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 *	create - create a new ProjectGroup in the database.
+	 *
+	 *	@param	string	The project name.
+	 *	@param	string	The project description.
+	 *	@param	int	Whether it is (1) public or (0) private .
+	 *	@param	string	The email address to send new notifications to.
+	 *	@return boolean success.
+	 */
+	function create($project_name,$description,$is_public=1,$send_all_posts_to='') {
+
+		global $Language;
+		
+		if (strlen($project_name) < 3) {
+			$this->setError($Language->getText('pm_projectgroup','error_min_name_length'));
+			return false;
+		}
+		if (strlen($description) < 10) {
+			$this->setError($Language->getText('pm_projectgroup','error_min_desc_length'));
+			return false;
+		}
+		if ($send_all_posts_to) {
+			$invalid_mails = validate_emails($send_all_posts_to);
+			if (count($invalid_mails) > 0) {
+				$this->setInvalidEmailError();
+				return false;
+			}
+		}
+		
+		$perm =& $this->Group->getPermission( session_get_user() );
+
+		if (!$perm || !is_object($perm) || !$perm->isPMAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		$sql="INSERT INTO project_group_list (group_id,project_name,is_public,
+			description,send_all_posts_to)
+			VALUES ('".$this->Group->getId()."','". htmlspecialchars($project_name) ."','$is_public',
+			'". htmlspecialchars($description) ."','$send_all_posts_to')";
+
+		db_begin();
+		$result=db_query($sql);
+		if (!$result) {
+			db_rollback();
+			$this->setError('Error Adding ProjectGroup: '.db_error());
+			return false;
+		}
+		$this->group_project_id=db_insertid($result,'project_group_list','group_project_id');
+		$this->fetchData($this->group_project_id);
+
+		if (!$this->addAllUsers()) {
+			db_rollback();
+			return false;
+		}
+
+		db_commit();
+		return true;
+	}
+
+	/**
+	 *  fetchData - re-fetch the data for this ProjectGroup from the database.
+	 *
+	 *  @param  int	 The project group ID.
+	 *  @return	boolean	success.
+	 */
+	function fetchData($group_project_id) {
+		$res=db_query("SELECT * FROM project_group_list_vw
+			WHERE group_project_id='$group_project_id'
+			AND group_id='". $this->Group->getID() ."'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('ProjectGroup:: Invalid group_project_id');
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	getGroup - get the Group object this ProjectGroup is associated with.
+	 *
+	 *	@return	object	The Group object.
+	 */
+	function &getGroup() {
+		return $this->Group;
+	}
+
+	/**
+	 *	getID - get this GroupProjectID.
+	 *
+	 *	@return	int	The group_project_id #.
+	 */
+	function getID() {
+		return $this->data_array['group_project_id'];
+	}
+
+	/**
+	 *	getOpenCount - get the count of open tracker items in this tracker type.
+	 *
+	 *	@return   int The count.
+	 */
+	function getOpenCount() {
+		return $this->data_array['open_count'];
+	}
+
+	/**
+	 *	getTotalCount - get the total number of tracker items in this tracker type.
+	 *
+	 *	@return   int The total count.
+	 */
+	function getTotalCount() {
+		return $this->data_array['count'];
+	}
+
+	/**
+	 *	isPublic - Is this projectGroup open to the general public.
+	 *
+	 *	@return boolean	allow.
+	 */
+	function isPublic() {
+		return $this->data_array['is_public'];
+	}
+
+	/**
+	 *	getName - get the name of this projectGroup.
+	 *
+	 *	@return string	The name of this projectGroup.
+	 */
+	function getName() {
+		return $this->data_array['project_name'];
+	}
+
+	/**
+	 *	getSendAllPostsTo - an optional email address to send all task updates to.
+	 *
+	 *	@return string	The email address.
+	 */
+	function getSendAllPostsTo() {
+		return $this->data_array['send_all_posts_to'];
+	}
+
+	/**
+	 *	getDescription - the description of this ProjectGroup.
+	 *
+	 *	@return string	The description.
+	 */
+	function getDescription() {
+		return $this->data_array['description'];
+	}
+
+	/**
+	 * getStatuses - Return result set of statuses.
+	 *
+	 * @returns Database result set.
+	 */
+	function getStatuses () {
+		if (!$this->statuses) {
+			$sql='SELECT * FROM project_status';
+			$this->statuses=db_query($sql);
+		}
+		return $this->statuses;
+	}
+
+	/**
+	 * getCategories - Return result set of categories.
+	 *
+	 * @returns Database result set.
+	 */
+	function getCategories () {
+		if (!$this->categories) {
+			$sql="SELECT category_id,category_name 
+				FROM project_category 
+				WHERE group_project_id='".$this->getID()."'";
+			$this->categories=db_query($sql);
+		}
+		return $this->categories;
+	}
+
+	/**
+	 *  getCategoryObjects - Array of ProjectCategory objects set up for this artifact type.
+	 *
+	 *  @return array   Of ProjectCategory objects.
+	 */
+	function &getCategoryObjects() {
+		$res = $this->getCategories();
+		$cats = array();
+		while ($arr = db_fetch_array($res)) {
+			$cats[] = new ProjectCategory($this,$arr);
+		}
+		return $cats;
+	}
+
+	/**
+	 * getTechnicians - Return a result set of pm technicians in this group.
+	 *
+	 * @returns Datbase result set.
+	 */
+	function getTechnicians () {
+		if (!$this->technicians) {
+			$sql="SELECT users.user_id,users.realname 
+				FROM users,project_perm 
+				WHERE users.user_id=project_perm.user_id 
+				AND project_perm.group_project_id='". $this->getID() ."' 
+				AND project_perm.perm_level IN (1,2) 
+				ORDER BY users.realname";
+			$this->technicians=db_query($sql);
+		}
+		return $this->technicians;
+	}
+
+	/**
+	 *  getTechnicianObjects - Array of User objects set up for this artifact type.
+	 *
+	 *  @return array   Of User objects.
+	 */
+	function &getTechnicianObjects() {
+		$res = $this->getTechnicians();
+		$arr =& util_result_column_to_array($res,0);
+		return user_get_objects($arr);
+	}
+
+	/**
+	 *	update - update a ProjectGroup in the database.
+	 *
+	 *	@param	string	The project name.
+	 *	@param	string	The project description.
+	 *	@param	string	The email address to send new notifications to.
+	 *	@return boolean success.
+	 */
+	function update($project_name,$description,$send_all_posts_to='') {
+
+		global $Language;
+		if (strlen($project_name) < 3) {
+			$this->setError($Language->getText('pm_projectgroup','error_min_name_length'));
+			return false;
+		}
+		if (strlen($description) < 10) {
+			$this->setError($Language->getText('pm_projectgroup','error_min_desc_length'));
+			return false;
+		}
+
+		if ($send_all_posts_to) {
+			$invalid_mails = validate_emails($send_all_posts_to);
+			if (count($invalid_mails) > 0) {
+				$this->setInvalidEmailError();
+				return false;
+			}
+		}
+
+
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		$sql="UPDATE project_group_list SET
+			project_name='". htmlspecialchars($project_name) ."',
+			description='". htmlspecialchars($description) ."',
+			send_all_posts_to='$send_all_posts_to'
+			WHERE group_id='".$this->Group->getID()."'
+			AND group_project_id='".$this->getID()."'";
+		$res=db_query($sql);
+
+		if (!$res || db_affected_rows($res) < 1) {
+			$this->setError('Error On Update: '.db_error().$sql);
+			return false;
+		}
+		return true;
+	}
+
+	/**
+	 *	delete - delete this subproject and all its related data.
+	 *
+	 *	@param  bool	I'm Sure.
+	 *	@param  bool	I'm REALLY sure.
+	 *	@return   bool true/false;
+	 */
+	function delete($sure, $really_sure) {
+		if (!$sure || !$really_sure) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		db_begin();
+		db_query("DELETE FROM project_category
+			WHERE group_project_id='".$this->getID()."'");
+//echo '1'.db_error();
+		db_query("DELETE FROM project_perm
+			WHERE group_project_id='".$this->getID()."'");
+//echo '2'.db_error();
+		db_query("DELETE FROM project_assigned_to
+			WHERE EXISTS (SELECT project_task_id FROM project_task
+			WHERE group_project_id='".$this->getID()."'
+			AND project_task.project_task_id=project_assigned_to.project_task_id)");
+//echo '4'.db_error();
+		db_query("DELETE FROM project_dependencies
+			WHERE EXISTS (SELECT project_task_id FROM project_task
+			WHERE group_project_id='".$this->getID()."'
+			AND project_task.project_task_id=project_dependencies.project_task_id)");
+//echo '5'.db_error();
+		db_query("DELETE FROM project_history
+			WHERE EXISTS (SELECT project_task_id FROM project_task
+			WHERE group_project_id='".$this->getID()."'
+			AND project_task.project_task_id=project_history.project_task_id)");
+//echo '6'.db_error();
+		db_query("DELETE FROM project_messages
+			WHERE EXISTS (SELECT project_task_id FROM project_task
+			WHERE group_project_id='".$this->getID()."'
+			AND project_task.project_task_id=project_messages.project_task_id)");
+//echo '7'.db_error();
+		db_query("DELETE FROM project_task_artifact
+			WHERE EXISTS (SELECT project_task_id FROM project_task
+			WHERE group_project_id='".$this->getID()."'
+			AND project_task.project_task_id=project_task_artifact.project_task_id)");
+//echo '8'.db_error();
+		db_query("DELETE FROM rep_time_tracking
+			WHERE EXISTS (SELECT project_task_id FROM project_task
+			WHERE group_project_id='".$this->getID()."'
+			AND project_task.project_task_id=rep_time_tracking.project_task_id)");
+//echo '9'.db_error();
+		db_query("DELETE FROM project_task
+			WHERE group_project_id='".$this->getID()."'");
+//echo '10'.db_error();
+		db_query("DELETE FROM project_group_list
+			WHERE group_project_id='".$this->getID()."'");
+//echo '11'.db_error();
+		db_query("DELETE FROM project_counts_agg
+			WHERE group_project_id='".$this->getID()."'");
+//echo '12'.db_error();
+		db_commit();
+		return true;
+	}
+
+	/**
+	 *  addAllUsers - add all users to this project.
+	 *
+	 *  @return boolean success.
+	 */
+	function addAllUsers() {
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		$sql="INSERT INTO project_perm (group_project_id,user_id,perm_level)
+			SELECT '".$this->getID()."',user_id,project_flags
+			FROM user_group
+			WHERE
+			group_id='".$this->Group->getID()."'
+			AND NOT EXISTS (SELECT user_id FROM project_perm
+			WHERE group_project_id='".$this->getID()."'
+			AND user_id=user_group.user_id);";
+		$res= db_query($sql);
+		if (!$res) {
+			$this->setError(db_error());
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 *  addUser - add a user to this subproject.
+	 *
+	 *  @param  int	 user_id of the new user.
+	 *  @return boolean success.
+	 */
+	function addUser($id) {
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		if (!$id) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		$sql="SELECT * FROM project_perm
+			WHERE group_project_id='".$this->getID()."'
+			AND user_id='$id'";
+		$result=db_query($sql);
+		if (db_numrows($result) > 0) {
+			return true;
+		} else {
+			$sql="INSERT INTO project_perm (group_project_id,user_id,perm_level)
+				VALUES ('".$this->getID()."','$id',0)";
+			$result=db_query($sql);
+			if ($result && db_affected_rows($result) > 0) {
+				return true;
+			} else {
+				$this->setError(db_error());
+				return false;
+			}
+		}
+	}
+
+	/**
+	 *  updateUser - update a user's permissions.
+	 *
+	 *  @param  int	 user_id of the user to update.
+	 *  @param  int	 (0) read only, (1) tech only, (2) admin & tech (3) admin only.
+	 *  @return boolean success.
+	 */
+	function updateUser($id,$perm_level) {
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		if (!$id) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		//
+		//  Update and test if it already exists
+		//
+		$sql="UPDATE project_perm SET perm_level='$perm_level'
+			WHERE user_id='$id' AND group_project_id='".$this->getID()."'";
+		$result=db_query($sql);
+		if (db_affected_rows($result) < 1) {
+			//
+			//  If not, insert it.
+			//
+			$sql="INSERT INTO project_perm (group_project_id,user_id,perm_level) VALUES
+				('".$this->getID()."','$id','$perm_level')";
+			$result=db_query($sql);
+			if (!$result) {
+				$this->setError(db_error());
+				return false;
+			} else {
+				return true;
+			}
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 *  deleteUser - delete a user's permissions.
+	 *
+	 *  @param  int	 user_id of the user who's permissions to delete.
+	 *  @return boolean success.
+	 */
+	function deleteUser($id) {
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		if (!$id) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		$sql="DELETE FROM project_perm
+			WHERE user_id='$id' AND group_project_id='".$this->getID()."'";
+		$result=db_query($sql);
+		if ($result) {
+			return true;
+		} else {
+			$this->setError(db_error());
+			return false;
+		}
+	}
+
+	/*
+
+		USER PERMISSION FUNCTIONS
+
+	*/
+
+	/**
+	 *	userCanView - determine if the user can view this subproject.
+	 *
+	 *	@return boolean   user_can_view.
+	 */
+	function userCanView() {
+		if ($this->isPublic()) {
+			return true;
+		} else {
+			if (!session_loggedin()) {
+				return false;
+			} else {
+				//
+				//  You must have an entry in project_perm if this subproject is not public
+				//
+				if ($this->getCurrentUserPerm() >= 0) {
+					return true;
+				} else {
+					return false;
+				}
+			}
+		}
+	}
+
+	/**
+	 *  userIsAdmin - see if the logged-in user's perms are >= 2 or Group PMAdmin.
+	 *
+	 *  @return boolean user_is_admin.
+	 */
+	function userIsAdmin() {
+		if (!session_loggedin()) {
+				return false;
+		} else {
+			$perm =& $this->Group->getPermission( session_get_user() );
+
+			if (($this->getCurrentUserPerm() >= 2) || ($perm->isPMAdmin())) {
+				return true;
+			} else {
+				return false;
+			}
+		}
+	}
+
+	/**
+	 *  userIsTechnician - see if the logged-in user's perms are >= 1 or Group PMAdmin.
+	 *
+	 *  @return boolean user_is_technician.
+	 */
+	function userIsTechnician() {
+		if (!session_loggedin()) {
+				return false;
+		} else {
+			$perm =& $this->Group->getPermission( session_get_user() );
+
+			if (($this->getCurrentUserPerm() >= 1) || ($perm->isPMAdmin())) {
+				return true;
+			} else {
+				return false;
+			}
+		}
+	}
+
+	/**
+	 *  getCurrentUserPerm - get the logged-in user's perms from project_perm.
+	 *
+	 *  @return int perm level for the logged-in user.
+	 */
+	function getCurrentUserPerm() {
+		if (!session_loggedin()) {
+			return -1;
+		} else {
+			if (!isset($this->current_user_perm)) {
+				$sql="select perm_level
+				FROM project_perm
+				WHERE group_project_id='". $this->getID() ."'
+				AND user_id='".user_getid()."'";
+				$this->current_user_perm=db_result(db_query($sql),0,0);
+			}
+			return $this->current_user_perm;
+		}
+	}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/pm/ProjectGroupFactory.class
===================================================================
--- trunk/gforge_base/gforge/common/pm/ProjectGroupFactory.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/pm/ProjectGroupFactory.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,137 @@
+<?php
+/**
+ * GForge Project Management Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: ProjectGroupFactory.class 4218 2005-03-29 15:04:35Z tperdue $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+/*
+
+	Project/Task Manager
+	By Tim Perdue, Sourceforge, 11/99
+	Heavy rewrite by Tim Perdue April 2000
+
+	Total rewrite in OO and GForge coding guidelines 12/2002 by Tim Perdue
+*/
+
+require_once('common/include/Error.class');
+require_once('common/pm/ProjectGroup.class');
+
+class ProjectGroupFactory extends Error {
+
+	/**
+	 * The Group object.
+	 *
+	 * @var	 object  $Group.
+	 */
+	var $Group;
+
+	/**
+	 * The projectGroups array.
+	 *
+	 * @var	 array	projectGroups.
+	 */
+	var $projectGroups;
+
+	/**
+	 *  Constructor.
+	 *
+	 *	@param	object	The Group object to which this ProjectGroupFactory is associated.
+	 *	@return	boolean	success.
+	 */
+	function ProjectGroupFactory(&$Group) {
+		$this->Error();
+		if (!$Group || !is_object($Group)) {
+			$this->setError('ProjectGroup:: No Valid Group Object');
+			return false;
+		}
+		if ($Group->isError()) {
+			$this->setError('ProjectGroup:: '.$Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $Group;
+
+		return true;
+	}
+
+	/**
+	 *	getGroup - get the Group object this ProjectGroupFactory is associated with.
+	 *
+	 *	@return	object	The Group object.
+	 */
+	function &getGroup() {
+		return $this->Group;
+	}
+
+	/**
+	 *	getProjectGroups - get an array of ProjectGroup objects.
+	 *
+	 *	@return	array	The array of ProjectGroups.
+	 */
+	function &getProjectGroups() {
+		
+		global $Language;
+		if ($this->projectGroups) {
+			return $this->projectGroups;
+		}
+		if (session_loggedin()) {
+			$perm =& $this->Group->getPermission( session_get_user() );
+			if (!$perm || !is_object($perm) || !$perm->isMember()) {
+				$public_flag='=1';
+			} else {
+				$public_flag='<3';
+				if ($perm->isPMAdmin()) {
+					$exists='';
+				} else {
+					$exists=" AND EXISTS (SELECT group_project_ID 
+					FROM project_perm 
+					WHERE perm_level >= 0 AND group_project_id=project_group_list.group_project_id
+					AND user_id='".user_getid()."') ";
+				}
+			}
+		} else {
+			$public_flag='=1';
+		}
+
+		$sql="SELECT *
+			FROM project_group_list_vw
+			WHERE group_id='". $this->Group->getID() ."' 
+			AND is_public $public_flag $exists
+			ORDER BY group_project_id;";
+
+		$result = db_query ($sql);
+
+		$rows = db_numrows($result);
+
+		if (!$result || $rows < 1) {
+			$this->setError($Language->getText('pm_projectgroup','none_found').db_error());
+			return false;
+		} else {
+			while ($arr = db_fetch_array($result)) {
+				$this->projectGroups[] = new ProjectGroup($this->Group, $arr['group_project_id'], $arr);
+			}
+		}
+		return $this->projectGroups;
+	}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/pm/ProjectTask.class
===================================================================
--- trunk/gforge_base/gforge/common/pm/ProjectTask.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/pm/ProjectTask.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,1097 @@
+<?php
+/**
+ * GForge Project Management Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: ProjectTask.class 5361 2006-03-09 17:21:22Z danper $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+/*
+
+	Project/Task Manager
+	By Tim Perdue, Sourceforge, 11/99
+	Heavy rewrite by Tim Perdue April 2000
+
+	Total rewrite in OO and GForge coding guidelines 12/2002 by Tim Perdue
+*/
+
+
+require_once('common/include/Error.class');
+require_once('common/include/Validator.class');
+
+function &projecttask_get_object($project_task_id,$data=false) {
+		global $PROJECTTASK_OBJ;
+		if (!isset($PROJECTTASK_OBJ["_".$project_task_id."_"])) {
+			if ($data) {
+				//the db result handle was passed in
+			} else {
+				$res=db_query("SELECT * FROM project_task_vw
+					WHERE project_task_id='$project_task_id'");
+
+				if (db_numrows($res) <1 ) {
+					$PROJECTTASK_OBJ["_".$project_task_id."_"]=false;
+					return false;
+				}
+				$data =& db_fetch_array($res);
+
+			}
+			$ProjectGroup =& projectgroup_get_object($data["group_project_id"]);
+			$PROJECTTASK_OBJ["_".$project_task_id."_"]= new ProjectTask($ProjectGroup,$project_task_id,$data);
+
+		}
+
+		return $PROJECTTASK_OBJ["_".$project_task_id."_"];
+}
+
+/*
+	Types of task dependencies
+*/
+define('PM_LINK_DEFAULT','FS');
+define('PM_LINK_START_START','SS');
+define('PM_LINK_START_FINISH','SF');
+define('PM_LINK_FINISH_START','FS');
+define('PM_LINK_FINISH_FINISH','FF');
+
+class ProjectTask extends Error {
+
+	/**
+	 * Associative array of data from db.
+	 *
+	 * @var	 array   $data_array.
+	 */
+	var $data_array;
+
+	/**
+	 * The ProjectGroup object.
+	 *
+	 * @var	 object  $ProjectGroup.
+	 */
+	var $ProjectGroup;
+	var $dependon;
+	var $assignedto;
+	var $relatedartifacts;
+
+	/**
+	 *  Constructor.
+	 *
+	 *	@param	object	The ProjectGroup object to which this ProjectTask is associated.
+	 *  @param  int	 The project_task_id.
+	 *  @param  array   The associative array of data.
+	 *	@return	boolean success.
+	 */
+	function ProjectTask(&$ProjectGroup, $project_task_id=false, $arr=false) {
+		$this->Error();
+		if (!$ProjectGroup || !is_object($ProjectGroup)) {
+			$this->setError('ProjectTask:: No Valid ProjectGroup Object');
+			return false;
+		}
+		if ($ProjectGroup->isError()) {
+			$this->setError('ProjectTask:: '.$ProjectGroup->getErrorMessage());
+			return false;
+		}
+		$this->ProjectGroup =& $ProjectGroup;
+
+		if ($project_task_id) {
+			if (!$arr || !is_array($arr)) {
+				if (!$this->fetchData($project_task_id)) {
+					return false;
+				}
+			} else {
+				$this->data_array =& $arr;
+				//
+				//	Verify this message truly belongs to this ProjectGroup
+				//
+				if ($this->data_array['group_project_id'] != $this->ProjectGroup->getID()) {
+					$this->setError('Group_project_id in db result does not match ProjectGroup Object');
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+
+	/**
+	 *	create - create a new ProjectTask in the database.
+	 *
+	 *	@param	string	The summary of this task.
+	 *	@param	string	The detailed description of this task.
+	 *	@param	int	The Priority of this task.
+	 *	@param	int	The Hours estimated to complete this task.
+	 *	@param	int	The (unix) start date of this task.
+	 *	@param	int	The (unix) end date of this task.
+	 *	@param	int	The category_id of this task.
+	 *	@param	int	The percentage of completion in integer format of this task.
+	 *	@param	array	An array of user_id's that are assigned this task.
+	 *	@param	array	An array of project_task_id's that this task depends on.
+	 *	@param	int	The duration of the task in days.
+	 *	@param	int	The id of the parent task, if any.
+	 *	@return	boolean success.
+	 */
+	function create($summary,$details,$priority,$hours,$start_date,$end_date,
+			$category_id,$percent_complete,&$assigned_arr,&$depend_arr,$duration=0,$parent_id=0) {
+		$v = new Validator();
+		$v->check($summary, "summary");
+		$v->check($details, "details");
+		$v->check($priority, "priority");
+		$v->check($hours, "hours");
+		$v->check($start_date, "start date");
+		$v->check($end_date, "end date");
+		$v->check($category_id, "category");
+		if (!$v->isClean()) {
+			$this->setError($v->formErrorMsg("Must include "));
+			return false;
+ 		}
+		if (!$parent_id) {
+			$parent_id=0;
+		}
+		if (!$duration) {
+			$duration=0;
+		}
+		if (!$this->ProjectGroup->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		db_begin();
+		$res=db_query("SELECT nextval('project_task_pk_seq') AS id");
+		if (!$project_task_id=db_result($res,0,'id')) {
+			$this->setError('Could Not Get Next ID');
+			db_rollback();
+			return false;
+		} else {
+			$this->data_array['project_task_id']=$project_task_id;
+
+			if (!$this->setDependentOn($depend_arr)) {
+				db_rollback();
+				return false;
+			} elseif (!$this->setAssignedTo($assigned_arr)) {
+				db_rollback();
+				return false;
+			} else {
+				$sql="INSERT INTO project_task (project_task_id,group_project_id,created_by,summary,
+					details,start_date,end_date,status_id,category_id,priority,percent_complete,hours,duration,parent_id) 
+					VALUES ('$project_task_id','". $this->ProjectGroup->getID() ."', '".user_getid()."', '". htmlspecialchars($summary) ."',
+					'". htmlspecialchars($details) ."','$start_date','$end_date','1','$category_id','$priority','$percent_complete','$hours','$duration','$parent_id')";
+
+				$result=db_query($sql);
+				if (!$result || db_affected_rows($result) < 1) {
+					$this->setError('ProjectTask::create() Posting Failed '.db_error().$sql);
+					db_rollback();
+					return false;
+				} else {
+					if (!$this->fetchData($project_task_id)) {
+						db_rollback();
+						return false;
+					} else {
+						$this->sendNotice(1);
+						db_commit();
+						return true;
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 *  fetchData - re-fetch the data for this ProjectTask from the database.
+	 *
+	 *  @param  int	 The project_task_id.
+	 *  @return	boolean	success.
+	 */
+	function fetchData($project_task_id) {
+		$res=db_query("SELECT * FROM project_task_vw
+			WHERE project_task_id='$project_task_id'
+			AND group_project_id='". $this->ProjectGroup->getID() ."'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('ProjectTask::fetchData() Invalid Task ID'.db_error());
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	getProjectGroup - get the ProjectGroup object this ProjectTask is associated with.
+	 *
+	 *	@return	Object	The ProjectGroup object.
+	 */
+	function &getProjectGroup() {
+		return $this->ProjectGroup;
+	}
+
+	/**
+	 *	getID - get this project_task_id.
+	 *
+	 *	@return	int	The project_task_id.
+	 */
+	function getID() {
+		return $this->data_array['project_task_id'];
+	}
+
+	/**
+	 *	getSubmittedRealName - get the real name of the person who created this task.
+	 *
+	 *	@return	string	The real name person who created this task.
+	 */
+	function getSubmittedRealName() {
+		return $this->data_array['realname'];
+	}
+	
+	/**
+	 *	getDuration - the duration of the task.
+	 *
+	 *	@return	int	The number of days of duration.
+	 */
+	function getDuration() {
+		return $this->data_array['duration'];
+	}
+	
+	/**
+	 *	getParentID - the task_id of the parent task, if any.
+	 *
+	 *	@return	string	The real name person who created this task.
+	 */
+	function getParentID() {
+		return $this->data_array['parent_id'];
+	}
+	
+	/**
+	 *	getSubmittedUnixName - get the unix name of the person who created this task.
+	 *
+	 *	@return	string	The unix name of the person who created this task.
+	 */
+	function getSubmittedUnixName() {
+		return $this->data_array['user_name'];
+	}
+
+	/**
+	 *	getSummary - get the subject/summary of this task.
+	 *
+	 *	@return	string	The summary.
+	 */
+	function getSummary() {
+		return $this->data_array['summary'];
+	}
+
+	/**
+	 *	getDetails - get the body/details of this task.
+	 *
+	 *	@return	string	The body/details.
+	 */
+	function getDetails() {
+		return $this->data_array['details'];
+	}
+
+	/**
+	 *	getPercentComplete - an integer between 0 and 100.
+	 *
+	 *	@return	int	The percentage of completion of this task.
+	 */
+	function getPercentComplete() {
+		return $this->data_array['percent_complete'];
+	}
+
+	/**
+	 *	getPriority - the priority, between 1 and 9 of this task.
+	 *
+	 *	@return	int	The priority.
+	 */
+	function getPriority() {
+		return $this->data_array['priority'];
+	}
+
+	/**
+	 *	getHours - the hours this task is expected to take.
+	 *
+	 *	@return	int	The hours.
+	 */
+	function getHours() {
+		return $this->data_array['hours'];
+	}
+
+	/**
+	 *	getStartDate - the unix time that this task will start.
+	 *
+	 *	@return	int	The unix start time of this task.
+	 */
+	function getStartDate() {
+		return $this->data_array['start_date'];
+	}
+
+	/**
+	 *	getEndDate - the unix time that this task will end.
+	 *
+	 *	@return	int	The unix end time of this task.
+	 */
+	function getEndDate() {
+		return $this->data_array['end_date'];
+	}
+
+	/**
+	 *	getStatusID - the integer of the status of this task.
+	 *
+	 *	@return	int	the status_id.
+	 */
+	function getStatusID() {
+		return $this->data_array['status_id'];
+	}
+
+	/**
+	 *	getStatusName - the string of the status of this task.
+	 *
+	 *	@return	string	the status_name.
+	 */
+	function getStatusName() {
+		return $this->data_array['status_name'];
+	}
+
+	/**
+	 *	getCategoryID - the category_id of this task.
+	 *
+	 *	@return	int	the category_id.
+	 */
+	function getCategoryID() {
+		return $this->data_array['category_id'];
+	}
+
+	/**
+	 *	getCategoryName - the category_name of this task.
+	 *
+	 *	@return	int	the category_name.
+	 */
+	function getCategoryName() {
+		return $this->data_array['category_name'];
+	}
+
+	/**
+	 *	getLastModifiedDate - the last_modified_date of this task.
+	 *
+	 *	@return	int	the last_modified_date.
+	 */
+	function getLastModifiedDate() {
+		return $this->data_array['last_modified_date'];
+	}
+	
+	/**
+	 *	setExternalID - set a row in project_task_external_order which stores 
+	 *	an id, for example an ID generated by MS Project, which needs to be restored later
+	 */
+	function setExternalID($id) {
+		$res=db_query("UPDATE project_task_external_order SET external_id='$id' 
+			WHERE project_task_id='".$this->getID()."'");
+		if (db_affected_rows($res) < 1) {
+			$res=db_query("INSERT INTO project_task_external_order (project_task_id,external_id) 
+				VALUES ('".$this->getID()."','$id')");
+		}
+	}
+
+	/**
+	 *	getExternalID - get the ID that MS Project uses to sort tasks
+	 *
+	 *	@return	int	the id.
+	 */
+	function getExternalID() {
+		return $this->data_array['external_id'];
+	}
+
+	/**
+	 *	getRelatedArtifacts - Return a result set of artifacts which are related to this task.
+	 *
+	 *	@returns Database result set.
+	 */
+	function getRelatedArtifacts() {
+		if (!$this->relatedartifacts) {
+			$this->relatedartifacts=
+			db_query("SELECT agl.group_id,agl.name,agl.group_artifact_id,a.artifact_id,a.open_date,a.summary 
+			FROM artifact_group_list agl, artifact a 
+			WHERE a.group_artifact_id=agl.group_artifact_id
+			AND EXISTS (SELECT artifact_id FROM project_task_artifact 
+				WHERE artifact_id=a.artifact_id
+				AND project_task_id='". $this->getID() ."')");
+		}
+		return $this->relatedartifacts;
+	}
+
+	/**
+	 *	addRelatedArtifacts - take an array of artifact_id's and build relationships.
+	 *
+	 *	@param	array	An array of artifact_id's to be attached to this task.
+	 *	@return	boolean	success.
+	 */
+	function addRelatedArtifacts($art_array) {
+		if (!$this->ProjectGroup->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+//
+//	SHOULD REALLY INSTANTIATE THIS ARTIFACT OBJECT TO ENSURE PROPER SECURITY - FUTURE
+//
+//	new ArtifactFromID($id)
+//
+		for ($i=0; $i<count($art_array); $i++) {
+			if ($art_array[$i] < 1) {
+				continue;
+			}
+			$res=db_query("INSERT INTO project_task_artifact (project_task_id,artifact_id) 
+				VALUES ('".$this->getID()."','".$art_array[$i]."')");
+			if (!$res) {
+				$this->setError('Error inserting artifact relationship: '.db_error());
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 *	removeRelatedArtifacts - take an array of artifact_id's and delete relationships.
+	 *
+	 *	@param	array	An array of artifact_id's to be removed from this task.
+	 *	@return	boolean	success.
+	 */
+	function removeRelatedArtifacts($art_array) {
+		if (!$this->ProjectGroup->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		for ($i=0; $i<count($art_array); $i++) {
+			$res=db_query("DELETE FROM project_task_artifact
+				WHERE project_task_id='".$this->getID()."'
+				AND artifact_id='".$art_array[$i]."'");
+			if (!$res) {
+				$this->setError('Error deleting artifact relationship: '.db_error());
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 *  delete - delete this tracker and all its related data.
+	 *
+	 *  @param  bool	I'm Sure.
+	 *  @return bool true/false;
+	 */
+	function delete($sure) {
+		if (!$sure) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		if (!$this->ProjectGroup->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		db_begin();
+
+		$res = db_query("DELETE FROM project_assigned_to WHERE project_task_id='".$this->getID()."'");
+		if (!$res) {
+			$this->setError('Error deleting assigned users relationship: '.db_error());
+			db_rollback();
+			return false;
+		}
+		$res = db_query("DELETE FROM project_dependencies WHERE project_task_id='".$this->getID()."'");
+		if (!$res) {
+			$this->setError('Error deleting dependencies: '.db_error());
+			db_rollback();
+			return false;
+		}
+		$res = db_query("DELETE FROM project_history WHERE project_task_id='".$this->getID()."'");
+		if (!$res) {
+			$this->setError('Error deleting history: '.db_error());
+			db_rollback();
+			return false;
+		}
+		$res = db_query("DELETE FROM project_messages WHERE project_task_id='".$this->getID()."'");
+		if (!$res) {
+			$this->setError('Error deleting messages: '.db_error());
+			db_rollback();
+			return false;
+		}
+		$res = db_query("DELETE FROM project_task_artifact	WHERE project_task_id='".$this->getID()."'");
+		if (!$res) {
+			$this->setError('Error deleting artifacts: '.db_error());
+			db_rollback();
+			return false;
+		}
+		$res = db_query("DELETE FROM rep_time_tracking	WHERE project_task_id='".$this->getID()."'");
+		if (!$res) {
+			$this->setError('Error deleting time tracking report: '.db_error());
+			db_rollback();
+			return false;
+		}
+		$res = db_query("DELETE FROM project_task WHERE project_task_id='".$this->getID()."'");
+		if (!$res) {
+			$this->setError('Error deleting task: '.db_error());
+			db_rollback();
+			return false;
+		}
+
+		db_commit();
+		return true;
+	}
+
+	/**
+	 *	getOtherTasks - Return a result set of tasks in this subproject that do not equal
+	 *	the current task_id.
+	 *
+	 *	@returns Database result set.
+	 */
+	function getOtherTasks () {
+		//
+		//	May not yet have an ID, if we are creating a NEW task
+		//
+		if ($this->getID()) {
+			$addstr=" AND project_task_id <> '". $this->getID() ."' ";
+		}
+		$sql="SELECT project_task_id,summary 
+		FROM project_task 
+		WHERE group_project_id='". $this->ProjectGroup->getID() ."' 
+		$addstr ORDER BY project_task_id DESC";
+		return db_query($sql);
+	}
+
+	/**
+	 *  getHistory - returns a result set of audit trail for this ProjectTask.
+	 *
+	 *  @return database result set.
+	 */
+	function getHistory() {
+		$sql="SELECT * 
+		FROM project_history_user_vw 
+		WHERE project_task_id='". $this->getID() ."' 
+		ORDER BY mod_date DESC";
+		return db_query($sql);
+	}
+
+	/**
+	 *  getMessages - get the list of messages attached to this ProjectTask.
+	 *
+	 *  @return database result set.
+	 */
+	function getMessages() {
+		$sql="select * 
+			FROM project_message_user_vw 
+			WHERE project_task_id='". $this->getID() ."' ORDER BY postdate DESC";
+		return db_query($sql);
+	}
+
+	/**
+	 * addMessage - Handle the addition of a followup message to this task.
+	 *
+	 * @param	   string  The message.
+	 * @returns	boolean	success.
+	 */
+	function addMessage($message) {
+		//prevent posting the same message
+		if ($this->getDetails() == htmlspecialchars($message)) {
+			return true;
+		}
+		$res=db_query("SELECT * FROM project_messages 
+			WHERE project_task_id='".$this->getID()."'
+			AND body='". htmlspecialchars($message) ."'");
+		if (!$res || db_numrows($res) < 1) {
+			$sql="INSERT INTO project_messages (project_task_id,body,posted_by,postdate) 
+				VALUES ('". $this->getID() ."','". htmlspecialchars($message) ."','".user_getid()."','". time() ."')";
+			$res=db_query($sql);
+			if (!$res || db_affected_rows($res) < 1) {
+				$this->setError('AddMessage():: '.db_error());
+				return false;
+			} else {
+				return true;
+			}
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 * addHistory - Handle the insertion of history for these parameters.
+	 *
+	 * @param	string  The field name.
+	 * @param	string  The old value.
+	 * @returns	boolean	success.
+	 */
+	function addHistory ($field_name,$old_value) {
+		$sql="insert into project_history(project_task_id,field_name,old_value,mod_by,mod_date) 
+			VALUES ('". $this->getID() ."','$field_name','$old_value','".user_getid()."','".time()."')";
+		$result=db_query($sql);
+		if (!$result) {
+			$this->setError('ERROR IN AUDIT TRAIL - '.db_error());
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 * checkCircular - recursive function calls itself to look at all tasks you are dependent on.
+	 *
+	 * @param	int	The project_task_id you are dependent on.
+	 * @param	int	The project_task_id you are checking circular dependencies for.
+	 * @returns	boolean	success.
+	 */
+	function checkCircular($depend_on_id, $original_id) {
+		//for msproject users - ms project has more complex logic than gforge
+		return true;
+
+		global $Language;
+		if ($depend_on_id == $original_id) {
+			$this->setError($Language->getText('pm_projecttask','circular_dependency'));
+	 		return false;
+		}
+
+		$res=db_query("SELECT is_dependent_on_task_id AS id 
+			FROM project_dependencies 
+			WHERE project_task_id='$depend_on_id'");
+		$rows=db_numrows($res);
+
+		for ($i=0; $i<$rows; $i++) {
+			if (!$this->checkCircular(db_result($res,$i,'id'), $original_id)) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * setDependentOn - takes an array of project_task_id's and builds dependencies.
+	 *
+	 * @param	array	The array of project_task_id's.
+	 * @returns	boolean	success.
+	 */
+	function setDependentOn(&$arr_) {
+//printr($arr_,'setDependentOn entry');
+//
+//	IMPORTANT - MUST VERIFY NO CIRCULAR DEPENDENCY!! 
+//
+		if (!$arr_ || empty($arr_)) {
+			$arr_=array('100'=>PM_LINK_DEFAULT);
+		}
+		$arr =& array_keys($arr_);
+		//get existing dependencies to diff against
+		$arr2 =& array_keys($this->getDependentOn());
+
+		if (count($arr) || count($arr2)) {
+			$add_arr = array_values (array_diff ($arr, $arr2));
+//echo "add arr: ".print_r($add_arr);
+			$del_arr = array_values (array_diff ($arr2, $arr));
+//echo "del arr: ".print_r($del_arr);
+			for ($i=0; $i<count($del_arr); $i++) {
+				db_query("DELETE FROM project_dependencies 
+					WHERE project_task_id='".$this->getID()."'
+					AND is_dependent_on_task_id='". $del_arr[$i] ."'");
+				if (db_error()) {
+					$this->setError('setDependentOn()-1:: '.db_error());
+					return false;
+				}
+			}
+			for ($i=0; $i<count($add_arr); $i++) {
+				//
+				//	Check task for circular dependencies
+				//
+				if (!$this->checkCircular($add_arr[$i],$this->getID())) {
+					return false;
+				}
+				$lnk = $arr_[$add_arr[$i]];
+				if (!$lnk) {
+					$lnk=PM_LINK_DEFAULT;
+				}
+				$sql="INSERT INTO project_dependencies (project_task_id,is_dependent_on_task_id,link_type) 
+					VALUES ('".$this->getID()."','". $add_arr[$i] ."','$lnk')";
+				db_query($sql);
+				if (db_error()) {
+					$this->setError('setDependentOn()-2:: '.db_error().$sql);
+					return false;
+				}
+			}
+			return true;
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 *	convertDependentOn - converts a regular array of dependencies, such 
+	 *	as from a multiple-select-box to an associative array with default 
+	 *	link types. Should be called from web code as part of the create/update calls.
+	 *  Here we are converting an array like array(1,5,9,77) to array(1=>SS,5=>SF,9=>FS,77=>SS)
+	 */
+	function &convertDependentOn($arr) {
+//echo "<p>Convert Dep0: ".print_r($arr);
+		$deps =& $this->getDependentOn();
+		for ($i=0; $i<count($arr); $i++) {
+			if ($deps[$arr[$i]]) {
+				//use existing link_type if it exists
+				$new[$arr[$i]]=$deps[$arr[$i]];
+			} else {
+				//else create with default link type
+				$new[$arr[$i]]=PM_LINK_DEFAULT;
+			}	
+		}
+//echo "<p>Convert Dep1: ".print_r($new);
+		return $new;
+	}
+
+	/**
+	 *	getDependentOn - get an array of project_task_id's that you are dependent on.
+	 *
+	 *	@return	array	The array of project_task_id's in this format: 
+	 *  array($id=>$link_type,id2=>link_type2).
+	 */
+	function &getDependentOn() {
+		if (!$this->getID()) {
+			return array();
+		}
+		if (!$this->dependon) {
+			$res=db_query("SELECT is_dependent_on_task_id,link_type
+				FROM project_dependencies
+				WHERE project_task_id='".$this->getID()."'");
+			for ($i=0; $i<db_numrows($res); $i++) {
+				$this->dependon[db_result($res,$i,'is_dependent_on_task_id')] = db_result($res,$i,'link_type');
+			}
+		}
+		/* fix bug 319: if dependentlist is emtpy, set it to 100 (none) */
+		if (!$this->dependon) {
+			$this->dependon[100]=PM_LINK_DEFAULT;
+		}
+		return $this->dependon;
+	}
+
+	/**
+	 * setAssignedTo - takes an array of user_id's and builds assignments.
+	 *
+	 * @param	array	The array of user_id's.
+	 * @returns	boolean	success.
+	 */
+	function setAssignedTo(&$arr) {
+		$arr2 =& $this->getAssignedTo();
+		$this->assignedto =& $arr;
+
+		//If no one is assigned, then assign it to "100" - NOBODY
+		if (count($arr) < 1 || ((count($arr)==1) && ($arr[0]==''))) {
+			$arr=array('100');
+		}
+		if (count($arr) || count($arr2)) {
+			$add_arr = array_values(array_diff ($arr, $arr2));
+			$del_arr = array_values(array_diff ($arr2, $arr));
+			for ($i=0; $i<count($del_arr); $i++) {
+				db_query("DELETE FROM project_assigned_to
+					WHERE project_task_id='".$this->getID()."'
+					AND assigned_to_id='". $del_arr[$i] ."'");
+				if (db_error()) {
+					$this->setError('setAssignedTo()-1:: '.db_error());
+					return false;
+				}
+			}
+			for ($i=0; $i<count($add_arr); $i++) {
+				db_query("INSERT INTO project_assigned_to (project_task_id,assigned_to_id) 
+					VALUES ('".$this->getID()."','". $add_arr[$i] ."')");
+				if (db_error()) {
+					$this->setError('setAssignedTo()-2:: '.db_error());
+					return false;
+				}
+			}
+			return true;
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 *	getAssignedTo - get an array of user_id's that you are assigned to.
+	 *
+	 *	@return	array	The array of user_id's.
+	 */
+	function &getAssignedTo() {
+		if (!$this->getID()) {
+			return array();
+		}
+		if (!$this->assignedto) {
+			$this->assignedto =& util_result_column_to_array(db_query("SELECT assigned_to_id 
+				FROM project_assigned_to 
+				WHERE project_task_id='".$this->getID()."'"));
+		}
+		return $this->assignedto;
+	}
+
+	/**
+	 *	update - update this ProjectTask in the database.
+	 *
+	 *	@param	string	The summary of this task.
+	 *	@param	string	The detailed description of this task.
+	 *	@param	int	The Priority of this task.
+	 *	@param	int	The Hours estimated to complete this task.
+	 *	@param	int	The (unix) start date of this task.
+	 *	@param	int	The (unix) end date of this task.
+	 *	@param	int	The status_id of this task.
+	 *	@param	int	The category_id of this task.
+	 *	@param	int	The percentage of completion in integer format of this task.
+	 *	@param	array	An array of user_id's that are assigned this task.
+	 *	@param	array	An array of project_task_id's that this task depends on.
+	 *	@param	int	The GroupProjectID of a new subproject that you want to move this Task to.
+	 *	@param	int	The duration of the task in days.
+	 *	@param	int	The id of the parent task, if any.
+	 *	@return	boolean success.
+	 */
+	function update($summary,$details,$priority,$hours,$start_date,$end_date,
+		$status_id,$category_id,$percent_complete,&$assigned_arr,&$depend_arr,
+		$new_group_project_id,$duration=0,$parent_id=0) {
+		$has_changes = false; // if any of the values passed is different from
+		$v = new Validator();
+		$v->check($summary, "summary");
+		$v->check($priority, "priority");
+		$v->check($hours, "hours");
+		$v->check($start_date, "start date");
+		$v->check($end_date, "end date");
+		$v->check($status_id, "status");
+		$v->check($category_id, "category");
+		if (!$v->isClean()) {
+			$this->setError($v->formErrorMsg("Must include "));
+			return false;
+		}
+		if (!$parent_id) {
+			$parent_id=0;
+		}
+		if ( ($this->getParentID()) != $parent_id ) {
+			$has_changes = true;
+		}
+		if (!$duration) {
+			$duration=0;
+		}
+		if ( ($this->getDuration()) != $duration ) {
+			$has_changes = true;
+		}
+
+		if (!$this->ProjectGroup->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		/*if ( ($this->getSummary() != $summary) || ($this->getDetails() != $details) ||
+			 ($this->getPriority() != $priority) || ($this->getHours() != $hours) ||
+			 ($this->getStartDate() != $start_date) || ($this->getEndDate() != $end_date) ||
+			 ($this->getStatusID() != $status_id) || ($this->getCategoryID() != $category_id) ||
+			 ($this->getPercentComplete() != $percent_complete) ) {
+			 
+			 $has_changes = true;
+		}*/
+		
+		
+		db_begin();
+
+		//
+		//  Attempt to move this Task to a new Subproject
+		//  need to instantiate new ProjectGroup obj and test if it works
+		//
+		$group_project_id = $this->ProjectGroup->getID();
+		if ($new_group_project_id != $group_project_id) {
+			$newProjectGroup= new ProjectGroup($this->ProjectGroup->getGroup(), $new_group_project_id);
+			if (!is_object($newProjectGroup) || $newProjectGroup->isError()) {
+				$this->setError('ProjectTask: Could not move to new ProjectGroup'. $newProjectGroup->getErrorMessage());
+				db_rollback();
+				return false;
+			}
+			/*  do they have perms for new ArtifactType?
+			if (!$newArtifactType->userIsAdmin()) {
+				$this->setPermissionDeniedError();
+				db_rollback();
+				return false;
+			}*/
+			//
+			//  Now set ProjectGroup, Category, and Assigned to 100 in the new ProjectGroup
+			//
+			$status_id=1;
+			$category_id='100';
+			unset($assigned_to);
+			$assigned_to=array('100');
+			$this->ProjectGroup =& $newProjectGroup;
+			$this->addHistory ('group_project_id',$group_project_id);
+			$has_changes = true;
+		}
+
+
+		if ($details) {
+			$has_changes = true;
+			if (!$this->addMessage($details)) {
+				db_rollback();
+				return false;
+			}
+		}
+		if ($this->getStatusID() != $status_id) { 
+			$this->addHistory ('status_id',$this->getStatusID());
+			$has_changes = true;
+		}
+
+		if ($this->getCategoryID() != $category_id)	{
+			$this->addHistory ('category_id',$this->getCategoryID());
+			$has_changes = true;
+		}
+
+		if ($this->getPriority() != $priority) {
+			$this->addHistory ('priority',$this->getPriority());
+			$has_changes = true;
+		}
+
+		if ($this->getSummary() != htmlspecialchars(stripslashes($summary))) {
+			$this->addHistory ('summary',addslashes($this->getSummary()));
+			$has_changes = true;
+		}
+
+		if ($this->getPercentComplete() != $percent_complete) {
+			$this->addHistory ('percent_complete',$this->getPercentComplete());
+			$has_changes = true;
+		}
+
+		if ($this->getHours() != $hours) {
+			$this->addHistory ('hours',$this->getHours());
+			$has_changes = true;
+		}
+
+		if ($this->getStartDate() != $start_date) {
+			$this->addHistory ('start_date',$this->getStartDate());
+			$has_changes = true;
+		}
+
+		if ($this->getEndDate() != $end_date) {
+			$this->addHistory ('end_date',$this->getEndDate());
+			$has_changes = true;
+		}
+
+		$old_assigned = &$this->getAssignedTo();
+		$diff_assigned_array=array_diff($old_assigned, $assigned_arr);
+		if (count($diff_assigned_array)>0) { 
+				for ($tmp=0;$tmp<count($old_assigned);$tmp++) { 
+					$this->addHistory('assigned_to_id',$old_assigned[$tmp]);
+				}
+				$has_changes = true;
+		}
+		$old_array =& array_keys($this->getDependentOn());			
+		$diff_array=array_diff($old_array,array_keys($depend_arr));
+		if (count($diff_array)>0) { 
+			for ($tmp=0;$tmp<count($old_array);$tmp++) {
+				$this->addHistory('dependent_on_id', $old_array[$tmp]);	
+			}
+			$has_changes = true;
+		}
+		
+		if (!$this->setDependentOn($depend_arr)) {
+			db_rollback();
+			return false;
+		} elseif (!$this->setAssignedTo($assigned_arr)) {
+			db_rollback();
+			return false;
+		} else {
+			$sql="UPDATE project_task SET
+				summary='".htmlspecialchars($summary)."',
+				priority='$priority',
+				hours='$hours',
+				start_date='$start_date',
+				end_date='$end_date',
+				status_id='$status_id',
+				percent_complete='$percent_complete',
+				category_id='$category_id',
+				group_project_id='$new_group_project_id',
+				duration='$duration',
+				parent_id='$parent_id'
+				WHERE group_project_id='$group_project_id'
+				AND project_task_id='".$this->getID()."'";
+
+			$res=db_query($sql);
+			if (!$res || db_affected_rows($res) < 1) {
+				$this->setError('Error On ProjectTask::update-5: '.db_error().$sql);
+				db_rollback();
+				return false;
+			} else {
+				if (!$this->fetchData($this->getID())) {
+					$this->setError('Error On ProjectTask::update-6: '.db_error());
+					db_rollback();
+					return false;
+				} else {
+					if ($has_changes) { //only send email if there was any change
+						$this->sendNotice();
+					}
+					db_commit();
+					return true;
+				}
+			}
+		}
+
+	}
+
+	/**
+	 *	sendNotice - contains the logic for sending email/jabber updates.
+	 *
+	 *	@return	boolean	success.
+	 */
+	function sendNotice($first=false) {
+		global $send_task_email;
+		if ($send_task_email===false) {
+			return true;
+		}
+		$ids =& $this->getAssignedTo();
+
+		//
+		//	See if there is anyone to send messages to
+		//
+		if (count($ids) < 1 && !$this->ProjectGroup->getSendAllPostsTo()) {
+			return true;
+		}
+
+		$body = "Task #". $this->getID() ." has been updated. ".
+			"\n\nProject: ". $this->ProjectGroup->Group->getPublicName() .
+			"\nSubproject: ". $this->ProjectGroup->getName() .
+			"\nSummary: ".util_unconvert_htmlspecialchars( $this->getSummary() ).
+			"\nComplete: ". $this->getPercentComplete() ."%".
+			"\nStatus: ". $this->getStatusName() .
+			"\n\nDescription: ". util_unconvert_htmlspecialchars( $this->getDetails() );
+
+		/*
+			Now get the followups to this task
+		*/
+		$result2=$this->getMessages();
+
+		$rows=db_numrows($result2);
+
+		if ($result2 && $rows > 0) {
+			$body .= "\n\nFollow-Ups:";
+			for ($i=0; $i<$rows;$i++) {
+				$body .= "\n\n-------------------------------------------------------";
+				$body .= "\nDate: ". date($GLOBALS['sys_datefmt'],db_result($result2,$i,'postdate'));
+				$body .= "\nBy: ".db_result($result2,$i,'user_name');
+				$body .= "\n\nComment:\n".util_unconvert_htmlspecialchars(db_result($result2,$i,'body'));
+			}
+		}
+		$body .= "\n\n-------------------------------------------------------".
+			"\nFor more info, visit:".
+			"\n\nhttp://".$GLOBALS['sys_default_domain']."/pm/task.php?func=detailtask&project_task_id=".
+				$this->getID() ."&group_id=".
+				$this->ProjectGroup->Group->getID() ."&group_project_id=". $this->ProjectGroup->getID();
+
+		$subject="[Task #". $this->getID() .'] '.
+			util_unconvert_htmlspecialchars( $this->getSummary() );
+
+		util_handle_message(array_unique($ids),$subject,$body,$this->ProjectGroup->getSendAllPostsTo());
+		return true;
+	}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/pm/ProjectTaskFactory.class
===================================================================
--- trunk/gforge_base/gforge/common/pm/ProjectTaskFactory.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/pm/ProjectTaskFactory.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,236 @@
+<?php
+/**
+ * GForge Project Management Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: ProjectTaskFactory.class 4424 2005-06-17 13:32:24Z marcelo $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+/*
+
+	Project/Task Manager
+	By Tim Perdue, Sourceforge, 11/99
+	Heavy rewrite by Tim Perdue April 2000
+
+	Total rewrite in OO and GForge coding guidelines 12/2002 by Tim Perdue
+*/
+
+require_once('common/include/Error.class');
+require_once('common/pm/ProjectTask.class');
+
+class ProjectTaskFactory extends Error {
+
+	/**
+	 * The ProjectGroup object.
+	 *
+	 * @var	 object  $ProjectGroup.
+	 */
+	var $ProjectGroup;
+
+	/**
+	 * The project_tasks array.
+	 *
+	 * @var  array  project_tasks.
+	 */
+	var $project_tasks;
+	var $order;
+	var $status;
+	var $category;
+	var $assigned_to;
+	var $offset;
+	var $max_rows;
+	var $fetched_rows;
+	var $view_type;
+
+	/**
+	 *  Constructor.
+	 *
+	 *	@param	object	The ProjectGroup object to which this ProjectTask is associated.
+	 *	@return	boolean	success.
+	 */
+	function ProjectTaskFactory(&$ProjectGroup) {
+		$this->Error();
+		if (!$ProjectGroup || !is_object($ProjectGroup)) {
+			$this->setError('ProjectTask:: No Valid ProjectGroup Object');
+			return false;
+		}
+		if ($ProjectGroup->isError()) {
+			$this->setError('ProjectTask:: '.$ProjectGroup->getErrorMessage());
+			return false;
+		}
+		$this->ProjectGroup =& $ProjectGroup;
+		$this->order='project_task_id';
+		$this->offset=0;
+
+		return true;
+	}
+
+	/**
+	 *	setup - sets up limits and sorts before you call getTasks().
+	 *
+	 *	@param	int	The offset - number of rows to skip.
+	 *	@param	string	The way to order - ASC or DESC.
+	 *	@param	int	The max number of rows to return.
+	 *	@param	string	Whether to set these prefs into the user_prefs table - use "custom".
+	 *	@param	int	Include this param if you want to limit to a certain assignee.
+	 *	@param	int	Include this param if you want to limit to a certain category.
+	 *	@param	string	What view mode the screen should be in.
+	 */
+	function setup($offset,$order,$max_rows,$set,$_assigned_to,$_status,$_category_id,$_view='') {
+//echo "<BR>offset: $offset| order: $order|max_rows: $max_rows|_assigned_to: $_assigned_to|_status: $_status|_category_id: $_category_id +";
+		if ((!$offset) || ($offset < 0)) {
+			$this->offset=0;
+		} else {
+			$this->offset=$offset;
+		}
+
+		if (session_loggedin()) {
+			$u =& session_get_user();
+		}
+
+		if ($order) {
+			if ($order=='project_task_id' || $order=='percent_complete'
+				|| $order=='summary' || $order=='start_date' || $order=='end_date' || $order=='priority') {
+				if (session_loggedin()) {
+					$u->setPreference('pm_task_order', $order);
+				}
+			} else {
+				$order = 'project_task_id';
+			}
+		} else {
+			if (session_loggedin()) {
+				$order = $u->getPreference('pm_task_order');
+			}
+		}
+		if (!$order) {
+			$order = 'project_task_id';
+		}
+		$this->order=$order;
+
+		if ($set=='custom') {
+			/*
+				if this custom set is different than the stored one, reset preference
+			*/
+			$pref_=$_assigned_to.'|'.$_status.'|'.$_category_id.'|'.$_view;
+			if (session_loggedin() && ($pref_ != $u->getPreference('pm_brow_cust'.$this->ProjectGroup->Group->getID()))) {
+				//echo 'setting pref';
+				$u->setPreference('pm_brow_cust'.$this->ProjectGroup->Group->getID(),$pref_);
+			}
+		} else {
+			if (session_loggedin()) {
+				if ($pref_=$u->getPreference('pm_brow_cust'.$this->ProjectGroup->Group->getID())) {
+					$prf_arr=explode('|',$pref_);
+					$_assigned_to=$prf_arr[0];
+					$_status=$prf_arr[1];
+					$_category_id=$prf_arr[2];
+					$_view=$prf_arr[3];
+				}
+			}
+		}
+		$this->status=$_status;
+		$this->assigned_to=$_assigned_to;
+		$this->category=$_category_id;
+		$this->view_type=$_view;
+
+		if (!$max_rows || $max_rows < 5) {
+			$max_rows=50;
+		}
+		$this->max_rows=$max_rows;
+	}
+
+	/**
+	 *	getTasks - get an array of ProjectTask objects.
+	 *
+	 *	@return	array	The array of ProjectTask objects.
+	 */
+	function &getTasks() {
+		if ($this->project_tasks) {
+			return $this->project_tasks;
+		}
+
+		//if status selected, and more to where clause
+		if ($this->status && ($this->status != 100)) {
+			//for open tasks, add status=100 to make sure we show all
+			$status_str="AND project_task_vw.status_id IN (".$this->status.(($this->status==1)?',100':'').")";
+		} else {
+			//no status was chosen, so don't add it to where clause
+			$status_str='';
+		}
+
+		//if assigned to selected, and more to where clause
+		if ($this->assigned_to) {
+			$assigned_str="AND project_assigned_to.assigned_to_id='".$this->assigned_to."'";
+			$assigned_str2=',project_assigned_to';
+			$assigned_str3='project_task_vw.project_task_id=project_assigned_to.project_task_id AND';
+
+		} else {
+			//no assigned to was chosen, so don't add it to where clause
+			$assigned_str='';
+			$assigned_str2='';
+			$assigned_str3='';
+		}
+
+		if ($this->category) {
+			$cat_str="AND project_task_vw.category_id='".$this->category."'";
+		} else {
+			$cat_str='';
+		}
+
+		//
+		//	sort using an external ID useful only to something like MS Project
+		//
+		if ($this->order=='external_id') {
+			$ext_str='natural left join project_task_external_order';
+			$ext_fld_str=',project_task_external_order.external_id';
+		} else {
+			$ext_str='';
+			$ext_fld_str='';
+		}
+
+/*
+select project_task_vw.*,project_assigned_to.* FROM project_task_vw,project_assigned_to 
+WHERE project_assigned_to.project_task_id=project_task_vw.project_task_id;
+*/
+		$sql="SELECT project_task_vw.* $ext_fld_str
+			FROM project_task_vw $ext_str $assigned_str2 
+			WHERE $assigned_str3 project_task_vw.group_project_id='". $this->ProjectGroup->getID() ."' 
+			$assigned_str $status_str $cat_str 
+			ORDER BY ".$this->order.(($this->order=='priority') ? ' DESC ':' ');
+
+//echo $sql;
+	
+		$result=db_query($sql,($this->max_rows),$this->offset);
+		$rows = db_numrows($result);
+		$this->fetched_rows=$rows;
+		if (db_error()) {
+			$this->setError('Database Error: '.db_error().$sql);
+			return false;
+		} else {
+			$this->project_tasks = array();
+			while ($arr =& db_fetch_array($result)) {
+				$this->project_tasks[] = new ProjectTask($this->ProjectGroup, $arr['project_task_id'], $arr);
+			}
+		}
+		return $this->project_tasks;
+	}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/pm/ProjectTasksForUser.class
===================================================================
--- trunk/gforge_base/gforge/common/pm/ProjectTasksForUser.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/pm/ProjectTasksForUser.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,108 @@
+<?php
+/**
+ * GForge Project Management Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+
+require_once('common/pm/ProjectGroup.class');
+require_once('common/pm/ProjectTask.class');
+require_once('common/include/Group.class');
+require_once('common/include/Error.class');
+
+/**
+*	A class that manages the project tasks for a specific user
+*/
+class ProjectTasksForUser extends Error {
+
+	/**
+	* The User to whom the tasks belong
+	*/
+	var $User;
+
+	/**
+	* Creates a new ProjectTasksForUser object
+	*
+	* @param	user	the User object
+	*/
+	function ProjectTasksForUser(&$user) {
+		$this->User =& $user;
+		return true;
+	}
+
+	/**
+	* Gets a list of tasks for this user
+	*
+	* @param the SQL query to use to fetch the tasks
+	*	@return	an array of ProjectTask objects
+	*/
+	function &getTasksFromSQL ($sql) {
+		$tasks = array();
+		$result=db_query($sql);
+		$rows=db_numrows($result);
+		for ($i=0; $i < $rows; $i++) {
+			$project_task_id = db_result($result,$i,'project_task_id');
+			$arr =& db_fetch_array($result);
+			$task =& projecttask_get_object($project_task_id,$arr);
+			$tasks[] =& $task;
+		}
+		return $tasks;
+	}
+
+	/**
+	*	Gets a list of tasks by group project name
+	*
+	* @return an array of ProjectTask objects
+	*/
+	function &getTasksByGroupProjectName () {
+		$sql = "SELECT ptv.*,g.group_name,pgl.project_name 
+			FROM project_task_vw ptv,
+				project_assigned_to pat,
+				groups g,
+				project_group_list pgl
+			WHERE ptv.project_task_id=pat.project_task_id
+				AND pgl.group_id=g.group_id
+				AND pgl.group_project_id=ptv.group_project_id
+				AND ptv.status_id=1
+				AND pat.assigned_to_id='".$this->User->getID()."'
+			ORDER BY group_name,project_name";
+		return $this->getTasksFromSQL($sql);
+	}
+	
+	function &getTasksForToday() {
+		$now = getdate();
+		$today = mktime (18, 00, 00, $now['mon'], $now['mday'], $now['year']);
+		
+		$sql = "SELECT ptv.*,g.group_name,pgl.project_name 
+			FROM project_task_vw ptv,
+				project_assigned_to pat,
+				groups g,
+				project_group_list pgl
+			WHERE ptv.project_task_id=pat.project_task_id
+				AND pgl.group_id=g.group_id
+				AND pgl.group_project_id=ptv.group_project_id
+				AND ptv.start_date < '$today'
+				AND ptv.status_id=1
+				AND pat.assigned_to_id='".$this->User->getID()."'
+			ORDER BY group_name,project_name";
+		return $this->getTasksFromSQL($sql);
+	}
+}
+?>

Added: trunk/gforge_base/gforge/common/pm/Validator.class
===================================================================
--- trunk/gforge_base/gforge/common/pm/Validator.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/pm/Validator.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,75 @@
+<?php
+/**
+ * GForge Project Management Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+
+/**
+* This class is a simple utility to validate fields
+* 
+* Sample usage:
+*
+* $v = new Validator();
+* $v->check($summary, "summary");
+* $v->check($detail, "detail");
+* if (!$v->isClean()) {
+*  print $v->formErrorMsg("The following fields were null:");
+* }
+*
+*/
+class Validator {
+	var $badfields;
+
+	/**
+	* Checks to see if a field is null; if so, the field name is added to an internal array
+	*
+	* @param field - a variable to check for null
+	* @param name - the variable name
+	*/
+	function check($field, $name) {
+		if (!$field) {
+			$this->badfields[] = $name;
+		}
+	}
+
+	/**
+	* Returns true if no null fields have been checked so far
+	*
+	* @return boolean - True if there are no null fields so far
+	*/
+	function isClean() {
+		return count($this->badfields) == 0;
+	}
+	
+	/**
+	* Returns an error message which contains the null field names which have been checked
+	*
+	* @param preamble string - A string with which to start the error message
+	* @return string - A complete error message
+	*/
+	function formErrorMsg($preamble) {
+		foreach ($this->badfields as $field) {
+			$preamble = $preamble.$field.",";
+		}
+		return substr($preamble, 0, strlen($preamble)-1);
+	}
+}
+?>

Added: trunk/gforge_base/gforge/common/pm/import_utils.php
===================================================================
--- trunk/gforge_base/gforge/common/pm/import_utils.php	                        (rev 0)
+++ trunk/gforge_base/gforge/common/pm/import_utils.php	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,309 @@
+<?php
+require_once('common/pm/ProjectTaskFactory.class');
+
+function printr($var, $name='$var', $echo=true) {
+/*
+//	return;
+	//$str = highlight_string("<?php\n$name = ".var_export($var, 1).";\n? >\n", 1);
+//	$str=var_export($var, 1);
+	if ($echo) {
+		if (is_array($var)) {
+			$var =var_export($var, 1);
+		}
+//		echo $str;
+//	} else {
+		$fp=fopen('/tmp/msp.log','a');
+		fwrite($fp,"\n-------".date('Y-m-d H:i:s')."-----".$name."-----\n".$var);
+		fclose($fp);
+	}
+*/
+}
+
+function printrcomplete() {
+//	exec("/bin/cat /tmp/msp.log | mail -s\"printr\" tim at gforgegroup.com");
+//	exec("/bin/rm -f /tmp/msp.log");
+}
+
+function &pm_import_tasks($group_project_id,&$tasks) {
+	printr($tasks,'MSPCheckin::in-array');
+	printr(getenv('TZ'),'MSPCheckin::entry TZ');
+
+	$pg =& projectgroup_get_object($group_project_id);
+	if (!$pg || !is_object($pg)) {
+		$array['success']=false;
+		$array['errormessage']='Could Not Get ProjectGroup';
+	} elseif ($pg->isError()) {
+		$array['success']=false;
+		$array['errormessage']='Could Not Get ProjectGroup: '.$pg->getErrorMessage();
+	} else {
+		$count=count($tasks);
+//printr($count,'count - count of tasks');
+		//
+		//  Build hash list of technicians so we can get their ID for assigning tasks
+		//
+		$techs =& $pg->getTechnicianObjects();
+		$tcount=count($techs);
+		for ($i=0; $i<$tcount; $i++) {
+			$tarr[strtolower($techs[$i]->getUnixName())]=$techs[$i]->getID();
+			$tarr[strtolower($techs[$i]->getRealName())]=$techs[$i]->getID();
+		}
+		$invalid_names=array();
+		//
+		//  Create a linked list based on the msproj_id
+		//
+		for ($i=0; $i<$count; $i++) {
+			$msprojid[$tasks[$i]['msproj_id']] =& $tasks[$i];
+			$resrc = $tasks[$i]['resources'];
+			for ($j=0; $j<count($resrc); $j++) {
+				//validate user - see if they really exist as techs in this subproject
+				if (!$tarr[strtolower($resrc[$j]['user_name'])]) {
+					//create list of bogus names to send back
+					if (array_search(strtolower($resrc[$j]['user_name']),$invalid_names) === false) {
+						$invalid_names[]=$resrc[$j]['user_name'];
+					}
+				}
+			}
+		}
+
+		//
+		//  invalid assignees - send it back for remapping
+		//
+		if (count($invalid_names)) {
+			$array['success']=false;
+			$array['errormessage']='Invalid Resource Name';
+			$array['resourcename']=$invalid_names;
+			for ($i=0; $i<$tcount; $i++) {
+				$array['usernames'][$techs[$i]->getID()]=$techs[$i]->getUnixName();
+			}
+			return $array;
+		} else {
+			//
+			//  Begin inserting/updating the tasks
+			//
+			for ($i=0; $i<$count; $i++) {
+				if ($was_error) {
+					continue;
+				}
+				//no task_id so it must be new - create it
+				if (!$tasks[$i]['id']) {
+					if (!$tasks[$i]['notes']) {
+						$tasks[$i]['notes']='None Provided';
+					}
+					//create the task
+					$pt = new ProjectTask($pg);
+					if (!$pt || !is_object($pt)) {
+						$array['success']=false;
+						$was_error=true;
+						$array['errormessage']='Could Not Get ProjectTask';
+					} elseif ($pt->isError()) {
+						$array['success']=false;
+						$was_error=true;
+						$array['errormessage']='Could Not Get ProjectTask: '.$pt->getErrorMessage();
+					} else {
+						//remap priority names=>numbers
+						$priority=$tasks[$i]['priority'];
+						if (!$priority || $priority < 1 || $priority > 5) {
+			//				printr($priority,'Invalid Priority On New Task');
+							$priority=3;
+						}
+						//map users
+						$assignees=array();
+						$resrc = $tasks[$i]['resources'];
+						for ($ucount=0; $ucount< count($resrc); $ucount++) {
+							//get their user_id from the $tarr we created earlier
+							$assignees[]=$tarr[strtolower($resrc[$ucount]['user_name'])];
+						}
+						//don't do anything with dependencies yet - we may only have
+						//the MSprojid from dependent items
+						$hours = $tasks[$i]['work'];
+						if (!$hours) {
+							$hours='0.0';
+						}
+						$percent_complete= intval($tasks[$i]['percent_complete']);
+						if (!$percent_complete) {
+							$percent_complete=0;
+						} elseif ($percent_complete > 100) {
+							$percent_complete=100;
+						}
+						if (!$pt->create(
+							addslashes($tasks[$i]['name']),
+							addslashes($tasks[$i]['notes']),
+							$priority,
+							$hours,
+							strtotime($tasks[$i]['start_date']),
+							strtotime($tasks[$i]['end_date']),
+							100,
+							$percent_complete,
+							$assignees,
+							$deps = array(),
+							$tasks[$i]['duration'],
+							$tasks[$i]['parent_id'])) {
+							$array['success']=false;
+							$was_error=true;
+							$array['errormessage']='Error Creating ProjectTask: '.$pt->getErrorMessage();
+							break 1;
+//							continue;
+						} else {
+//successful
+							$tasks[$i]['id']  = $pt->getID();
+							$tasks[$i]['obj'] = $pt;
+							$pt->setExternalID($tasks[$i]['msproj_id']);
+							$pt = null;
+						}
+					}
+
+				} else {
+					//update existing task
+					//create the task
+					$pt = &projecttask_get_object($tasks[$i]['id']);
+					if (!$pt || !is_object($pt)) {
+						printr($tasks[$i]['id'],'Could not get task');
+					//	$array['success']=false;
+					//	$was_error=true;
+					//	$array['errormessage']='Could Not Get ProjectTask';
+					} elseif ($pt->isError()) {
+						printr($tasks[$i]['id'],'Could not get task - error in task');
+						$array['success']=false;
+						$was_error=true;
+						$array['errormessage']='Could Not Get ProjectTask: '.$pt->getErrorMessage();
+					} else {
+						//remap priority names=>numbers
+						$priority=$tasks[$i]['priority'];
+						if (!$priority || $priority < 1 || $priority > 5) {
+							printr($priority,'Invalid Priority On Existing Task');
+							$priority=3;
+						}
+						//map users
+						$assignees=array();
+						$resrc = $tasks[$i]['resources'];
+						for ($ucount=0; $ucount<count($resrc); $ucount++) {
+							//get their user_id from the $tarr we created earlier
+							$assignees[]=$tarr[strtolower($resrc[$ucount]['user_name'])];
+						}
+						//don't do anything with dependencies yet - we may only have the
+						//MSprojid from dependent items
+						$hours = $tasks[$i]['work'];
+						if (!$hours) {
+							$hours='0.0';
+						}
+						$percent_complete= intval($tasks[$i]['percent_complete']);
+						if (!$percent_complete) {
+							$percent_complete=0;
+						} elseif ($percent_complete > 100) {
+							$percent_complete=100;
+						}
+						if (!$pt->update(
+							addslashes($tasks[$i]['name']),
+							addslashes($tasks[$i]['notes']),
+							$priority,
+							$hours,
+							strtotime($tasks[$i]['start_date']),
+							strtotime($tasks[$i]['end_date']),
+							$pt->getStatusID(),
+							$pt->getCategoryID(),
+							$percent_complete,
+							$assignees,
+							$pt->getDependentOn(),
+							$pg->getID(),
+							$tasks[$i]['duration'],
+							$tasks[$i]['parent_id'])) {
+							$array['success']=false;
+							$was_error=true;
+							$array['errormessage']='Error Updating ProjectTask: '.$pt->getErrorMessage();
+							break 1;
+//							continue;
+
+						} else {
+//successful
+							$tasks[$i]['id']  = $pt->getID();
+							$tasks[$i]['obj'] = $pt;
+							$pt->setExternalID($tasks[$i]['msproj_id']);
+							$pt = null;
+
+						}
+					} //if task->iserror()
+				} //if task_id
+				//accumulate list of completed tasks
+				//any task not in this list will be deleted
+				$completed[$tasks[$i]['id']]=true;
+			} //for i
+
+
+			//
+			//  Do task dependencies
+			//
+
+			printr($was_error,'Right before deps');
+			if (!$was_error) {
+				//iterate the tasks
+				for ($i=0; $i<$count; $i++) {
+					$darr=$tasks[$i]['dependenton'];
+				
+					$deps=array();
+					//iterate each dependency in a task
+					for ($dcount=0; $dcount<count($darr); $dcount++) {
+						//get the id of the task we're dependent on -
+						// may have to get it from msprojid linked list
+						$id=$darr[$dcount]['task_id'];
+						if ($id < 1) {
+							$id=$msprojid[$darr[$dcount]['msproj_id']]['id'];
+						}
+						//prevent task from being dependent on itself
+						if ($id == $tasks[$i]['id']) {
+							continue;
+						}
+						$deps[$id]=$darr[$dcount]['link_type'];
+					}
+					printr($deps,'Deps for task id: '.$tasks[$i]['id']);
+					if (is_object($tasks[$i]['obj'])) {
+						printr($deps,'11 Done Setting deps for task id: '.$tasks[$i]['id']);
+						if (!$tasks[$i]['obj']->setDependentOn($deps)) {
+							$was_error=true;
+							$array['success']=false;
+							printr($tasks[$i]['obj'],'FAILED TO SET DEPENDENCIES: '.$tasks[$i]['obj']->getErrorMessage());
+						}
+						printr($deps,'22 Done Setting deps for task id: '.$tasks[$i]['id']);
+					} else {
+		//				$was_error=true;
+		//				$array['success']=false;
+						printr($foo,'PROJECT TASK OBJECT DOES NOT EXIST IN OBJ ARRAY');
+					}
+					printr($deps,'Done Setting deps for task id: '.$tasks[$i]['id']);
+					unset($deps);
+				} //iterates tasks to do dependencies
+			}
+
+
+			//
+			//	Delete unreferenced tasks
+			//
+			printr($was_error,'Right before deleting unreferenced tasks');
+			if (!$was_error) {
+				$ptf =& new ProjectTaskFactory($pg);
+				$pt_arr=& $ptf->getTasks();
+				for ($i=0; $i<count($pt_arr); $i++) {
+					if (is_object($pt_arr[$i])) {
+						if (!$completed[$pt_arr[$i]->getID()]) {
+							printr($pt_arr[$i]->getID(),'Deleting task');
+							if (!$pt_arr[$i]->delete(true)) {
+								echo $pt_arr[$i]->getErrorMessage();
+							} else {
+								printr($foo,'Deleting Unreferenced Tasks');
+							}
+						}
+					}
+				}
+			}
+		} //invalid names
+	} //get projectGroup
+
+	if (!$was_error) {
+		$array['success']=true;
+	}
+
+//	printr($array,'MSPCheckin::return-array');
+	printr(getenv('TZ'),'MSPCheckin::exit TZ');
+	return $array;
+}
+
+?>

Added: trunk/gforge_base/gforge/common/reporting/Report.class
===================================================================
--- trunk/gforge_base/gforge/common/reporting/Report.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/reporting/Report.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,139 @@
+<?php
+/**
+ * Reporting System
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: Report.class 3197 2004-07-28 17:34:45Z tperdue $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2003-03-16
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+DEFINE('REPORT_DAY_SPAN',24*60*60);
+DEFINE('REPORT_WEEK_SPAN',7*24*60*60);
+DEFINE('REPORT_MONTH_SPAN',30*24*60*60);
+
+DEFINE('REPORT_TYPE_DAILY',1);
+DEFINE('REPORT_TYPE_WEEKLY',2);
+DEFINE('REPORT_TYPE_MONTHLY',3);
+DEFINE('REPORT_TYPE_OA',4);
+
+class Report extends Error {
+
+//var $adjust_days=array('Sun'=>0, 'Sat'=>6, 'Fri'=>5, 'Thu'=>4, 'Wed'=>3, 'Tue'=>2, 'Mon'=>1);
+var $adjust_days=array('Sun'=>'0.0', 'Sat'=>1, 'Fri'=>2, 'Thu'=>3, 'Wed'=>4, 'Tue'=>5, 'Mon'=>6);
+var $month_start_arr=array();
+var $week_start_arr=array();
+var $site_start_date;
+var $data;
+var $labels;
+var $span;
+var $start_date;
+var $end_date;
+var $span_name=array(1=>'Daily',2=>'Weekly',3=>'Monthly',4=>'OverAll');
+var $graph_interval=array(1=>7,2=>1,3=>1,4=>1);
+
+function Report() {
+	$this->Error();
+	//
+	//	All reporting action will be done in GMT timezone
+	//
+	putenv('TZ=GMT');
+}
+
+/**
+ *	get the unix time that this install was setup.
+ */
+function getMinDate() {
+	if (!$this->site_start_date) {
+		$res=db_query("select min(add_date) AS start_date from users where add_date > 0;");
+		$this->site_start_date=db_result($res,0,'start_date');
+	}
+	return $this->site_start_date;
+}
+
+function &getMonthStartArr() {
+	if (count($this->month_start_arr) < 1) {
+		$min_date=$this->getMinDate();
+		for ($i=0; $i<24; $i++) {
+			$this->month_start_arr[]=mktime(0,0,0,date('m')+1-$i,1,date('Y'));
+			if ($this->month_start_arr[$i] < $min_date) {
+				break;
+			}
+		}
+	}
+	return $this->month_start_arr;
+}
+
+function &getWeekStartArr() {
+	if (count($this->week_start_arr) < 1) {
+		$min_date=$this->getMinDate();
+		$start=mktime(0,0,0,date('m'),(date('d')+$this->adjust_days[date('D')]),date('Y'));
+		for ($i=0; $i<104; $i++) {
+			$this->week_start_arr[]=($start-REPORT_WEEK_SPAN*$i);
+			if ($this->week_start_arr[$i] < $min_date) {
+				break;
+			}
+		}
+	}
+	return $this->week_start_arr;
+}
+
+function setSpan($span) {
+	$this->span=$span;
+}
+
+function getSpanName() {
+	return $this->span_name[$this->span];
+}
+
+function setData($result,$column) {
+	$this->data =& util_result_column_to_array($result,$column);
+}
+
+function setDates($result,$column) {
+	$arr =& util_result_column_to_array($result,$column);
+	for ($i=0; $i<count($arr); $i++) {
+		$this->labels[$i] = date('M d',$arr[$i]);
+	}
+}
+
+function getGraphInterval() {
+	return $this->graph_interval[$this->span];
+}
+
+function &getData() {
+	return $this->data;
+}
+
+function &getDates() {
+	return $this->labels;
+}
+
+function getStartDate() {
+	return $this->start_date;
+}
+
+function getEndDate() {
+	return $this->end_date;
+}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/reporting/ReportGroupAdded.class
===================================================================
--- trunk/gforge_base/gforge/common/reporting/ReportGroupAdded.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/reporting/ReportGroupAdded.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Reporting System
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: ReportGroupAdded.class 3475 2004-10-15 19:53:18Z cbayle $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2003-03-16
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/reporting/Report.class');
+
+class ReportGroupAdded extends Report {
+
+function ReportGroupAdded($span,$start=0,$end=0) {
+	$this->Report();
+
+	if (!$start) {
+		$start=mktime(0,0,0,date('m'),1,date('Y'));;
+	}
+	if (!$end) {
+		$end=time();
+	} else {
+		$end--;
+	}
+
+	if (!$span || $span == REPORT_TYPE_MONTHLY) {
+
+		$res=db_query("SELECT * FROM rep_groups_added_monthly 
+			WHERE month BETWEEN '$start' AND '$end' ORDER BY month");
+
+	} elseif ($span == REPORT_TYPE_WEEKLY) {
+
+		$res=db_query("SELECT * FROM rep_groups_added_weekly 
+			WHERE week BETWEEN '$start' AND '$end' ORDER BY week");
+
+	} elseif ($span == REPORT_TYPE_DAILY) {
+
+		$res=db_query("SELECT * FROM rep_groups_added_daily 
+			WHERE day BETWEEN '$start' AND '$end' ORDER BY day ASC");
+
+	}
+
+	$this->start_date=$start;
+	$this->end_date=$end;
+
+	if (!$res || db_error()) {
+		$this->setError('ReportGroupAdded:: '.db_error());
+		return false;
+	}
+	$this->setSpan($span);
+	$this->setDates($res,0);
+	$this->setData($res,1);
+	return true;
+}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/reporting/ReportGroupCum.class
===================================================================
--- trunk/gforge_base/gforge/common/reporting/ReportGroupCum.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/reporting/ReportGroupCum.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Reporting System
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: ReportGroupCum.class 3475 2004-10-15 19:53:18Z cbayle $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2003-03-16
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/reporting/Report.class');
+
+class ReportGroupCum extends Report {
+
+function ReportGroupCum($span,$start=0,$end=0) {
+	$this->Report();
+
+	if (!$start) {
+		$start=mktime(0,0,0,date('m'),1,date('Y'));;
+	}
+	if (!$end) {
+		$end=time();
+	} else {
+		$end--;
+	}
+
+	if (!$span || $span == REPORT_TYPE_MONTHLY) {
+
+		$res=db_query("SELECT * FROM rep_groups_cum_monthly 
+			WHERE month BETWEEN '$start' AND '$end' ORDER BY month");
+
+	} elseif ($span == REPORT_TYPE_WEEKLY) {
+
+		$res=db_query("SELECT * FROM rep_groups_cum_weekly 
+			WHERE week BETWEEN '$start' AND '$end' ORDER BY week");
+
+	} elseif ($span == REPORT_TYPE_DAILY) {
+
+		$res=db_query("SELECT * FROM rep_groups_cum_daily 
+			WHERE day BETWEEN '$start' AND '$end' ORDER BY day ASC");
+
+	}
+
+	$this->start_date=$start;
+	$this->end_date=$end;
+
+	if (!$res || db_error()) {
+		$this->setError('ReportGroupAdded:: '.db_error());
+		return false;
+	}
+	$this->setSpan($span);
+	$this->setDates($res,0);
+	$this->setData($res,1);
+	return true;
+}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/reporting/ReportProjectAct.class
===================================================================
--- trunk/gforge_base/gforge/common/reporting/ReportProjectAct.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/reporting/ReportProjectAct.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Reporting System
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: ReportProjectAct.class 3197 2004-07-28 17:34:45Z tperdue $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2003-03-16
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/reporting/Report.class');
+
+class ReportProjectAct extends Report {
+
+var $res;
+
+function ReportProjectAct($span,$group_id,$start=0,$end=0) {
+	$this->Report();
+
+	if (!$start) {
+		$start=mktime(0,0,0,date('m'),1,date('Y'));;
+	}
+	if (!$end) {
+		$end=time();
+	} else {
+		$end--;
+	}
+
+	if (!$group_id) {
+		$this->setError('No group_id');
+		return false;
+	}
+	if (!$span || $span == REPORT_TYPE_MONTHLY) {
+
+		$res=db_query("SELECT * FROM rep_group_act_monthly 
+			WHERE group_id='$group_id' AND month BETWEEN '$start' AND '$end' ORDER BY month");
+
+	} elseif ($span == REPORT_TYPE_WEEKLY) {
+
+		$res=db_query("SELECT * FROM rep_group_act_weekly 
+			WHERE group_id='$group_id' AND week BETWEEN '$start' AND '$end' ORDER BY week");
+
+	} elseif ($span == REPORT_TYPE_DAILY) {
+
+		$res=db_query("SELECT * FROM rep_group_act_daily 
+			WHERE group_id='$group_id' AND day BETWEEN '$start' AND '$end' ORDER BY day ASC");
+
+	}
+
+	$this->start_date=$start;
+	$this->end_date=$end;
+
+	if (!$res || db_error()) {
+		$this->setError('ReportProjectAct:: '.db_error());
+		return false;
+	}
+	$this->setSpan($span);
+	$this->setDates($res,1);
+	$this->res=$res;
+	return true;
+}
+
+function &getTrackerOpened() {
+	return util_result_column_to_array($this->res,2);
+}
+
+function &getTrackerClosed() {
+	return util_result_column_to_array($this->res,3);
+}
+
+function &getForum() {
+	return util_result_column_to_array($this->res,4);
+}
+
+function &getDocs() {
+	return util_result_column_to_array($this->res,5);
+}
+
+function &getDownloads() {
+	return util_result_column_to_array($this->res,6);
+}
+
+function &getCVSCommits() {
+	return util_result_column_to_array($this->res,7);
+}
+
+function &getTaskOpened() {
+	return util_result_column_to_array($this->res,8);
+}
+
+function &getTaskClosed() {
+	return util_result_column_to_array($this->res,9);
+}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/reporting/ReportProjectTime.class
===================================================================
--- trunk/gforge_base/gforge/common/reporting/ReportProjectTime.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/reporting/ReportProjectTime.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,124 @@
+<?php
+/**
+ * Reporting System
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: ReportProjectTime.class 3197 2004-07-28 17:34:45Z tperdue $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2003-03-16
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/reporting/Report.class');
+
+class ReportProjectTime extends Report {
+
+function ReportProjectTime($group_id,$type,$start=0,$end=0) {
+	$this->Report();
+
+	if (!$start) {
+		$start=mktime(0,0,0,date('m'),1,date('Y'));;
+	}
+	if (!$end) {
+		$end=time();
+	} else {
+		$end--;
+	}
+
+	if (!$group_id) {
+		$this->setError('No User_id');
+		return false;
+	}
+
+	//
+	//	Task report
+	//
+	if (!$type || $type=='tasks') {
+
+		$res=db_query("SELECT pt.summary,sum(rtt.hours) AS hours 
+			FROM rep_time_tracking rtt, project_task pt, project_group_list pgl
+			WHERE pgl.group_project_id=pt.group_project_id
+			AND pgl.group_id='$group_id'
+			AND rtt.report_date BETWEEN '$start' AND '$end' 
+			AND rtt.project_task_id=pt.project_task_id
+			GROUP BY pt.summary
+			ORDER BY hours DESC");
+
+	//
+	//	Category report
+	//
+	} elseif ($type=='category') {
+
+		$res=db_query("SELECT rtc.category_name, sum(rtt.hours) AS hours 
+			FROM rep_time_tracking rtt, rep_time_category rtc, project_task pt, project_group_list pgl
+			WHERE pgl.group_id='$group_id' 
+			AND pgl.group_project_id=pt.group_project_id
+			AND rtt.project_task_id=pt.project_task_id
+			AND rtt.report_date BETWEEN '$start' AND '$end' 
+			AND rtt.time_code=rtc.time_code
+			GROUP BY rtc.category_name
+			ORDER BY hours DESC");
+
+	//
+	//	Percentage this user spent on a specific subproject
+	//
+	} elseif ($type=='subproject') {
+
+		$res=db_query("SELECT pgl.project_name, sum(rtt.hours) AS hours 
+			FROM rep_time_tracking rtt, project_task pt, project_group_list pgl
+			WHERE pgl.group_id='$group_id'
+			AND rtt.report_date BETWEEN '$start' AND '$end' 
+			AND rtt.project_task_id=pt.project_task_id
+			AND pt.group_project_id=pgl.group_project_id
+			GROUP BY pgl.project_name
+			ORDER BY hours DESC");
+
+	} else {
+
+	//
+	//	Biggest Users
+	//
+		$res=db_query("SELECT u.realname, sum(rtt.hours) AS hours 
+			FROM users u, rep_time_tracking rtt, project_task pt, project_group_list pgl
+			WHERE pgl.group_id='$group_id'
+			AND rtt.report_date BETWEEN '$start' AND '$end' 
+			AND rtt.project_task_id=pt.project_task_id
+			AND pt.group_project_id=pgl.group_project_id
+			AND u.user_id=rtt.user_id
+			GROUP BY u.realname
+			ORDER BY hours DESC");
+
+	}
+
+	$this->start_date=$start;
+	$this->end_date=$end;
+
+	if (!$res || db_error()) {
+		$this->setError('ReportUserAct:: '.db_error());
+		return false;
+	}
+
+	$this->labels = util_result_column_to_array($res,0);
+	$this->setData($res,1);
+	return true;
+}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/reporting/ReportSetup.class
===================================================================
--- trunk/gforge_base/gforge/common/reporting/ReportSetup.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/reporting/ReportSetup.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,1322 @@
+<?php
+/**
+ * Reporting System
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: ReportSetup.class 3475 2004-10-15 19:53:18Z cbayle $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2003-03-16
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/reporting/Report.class');
+
+class ReportSetup extends Report {
+
+function ReportSetup() {
+	$this->Report();
+
+}
+
+function initialSetup() {
+	$this->createTables();
+	if (!$this->initialData()) {
+		return false;
+	} else {
+		return true;
+	}
+}
+
+function createTables() {
+
+//time tracking
+//DROP TABLE rep_time_category;
+	$sql[]="CREATE TABLE rep_time_category (
+	time_code serial UNIQUE,
+	category_name text
+	);";
+	//$sql[]="DROP TABLE rep_time_tracking;";
+	$sql[]="CREATE TABLE rep_time_tracking (
+	week int not null,
+	report_date int not null,
+	user_id int not null,
+	project_task_id int not null,
+	time_code int not null CONSTRAINT reptimetrk_timecode REFERENCES rep_time_category(time_code),
+	hours float not null
+	);";
+//	$sql[]="CREATE UNIQUE INDEX reptimetrk_weekusrtskcde ON 
+//		rep_time_tracking (week,user_id,project_task_id,time_code);";
+	$sql[]="CREATE INDEX reptimetracking_userdate ON 
+		rep_time_tracking (user_id,week);";
+
+	$sql[]="INSERT INTO rep_time_category VALUES ('1','Coding');";
+	$sql[]="INSERT INTO rep_time_category VALUES ('2','Testing');";
+	$sql[]="INSERT INTO rep_time_category VALUES ('3','Meeting');";
+	$sql[]="SELECT setval('rep_time_category_time_code_seq',(SELECT max(time_code) FROM rep_time_category));";
+
+//added users
+	$sql[]="DROP TABLE rep_users_added_daily;";
+	$sql[]="CREATE TABLE rep_users_added_daily (
+	day int not null primary key,
+	added int not null default 0);";
+
+	$sql[]="DROP TABLE rep_users_added_weekly";
+	$sql[]="CREATE TABLE rep_users_added_weekly (
+	week int not null primary key,
+	added int not null default 0);";
+
+	$sql[]="DROP TABLE rep_users_added_monthly";
+	$sql[]="CREATE TABLE rep_users_added_monthly (
+	month int not null primary key,
+	added int not null default 0);";
+
+//cumulative users
+	$sql[]="DROP TABLE rep_users_cum_daily";
+	$sql[]="CREATE TABLE rep_users_cum_daily (
+	day int not null primary key,
+	total int not null default 0);";
+
+	$sql[]="DROP TABLE rep_users_cum_weekly";
+	$sql[]="CREATE TABLE rep_users_cum_weekly (
+	week int not null primary key,
+	total int not null default 0);";
+
+	$sql[]="DROP TABLE rep_users_cum_monthly";
+	$sql[]="CREATE TABLE rep_users_cum_monthly (
+	month int not null primary key,
+	total int not null default 0);";
+
+//added groups
+	$sql[]="DROP TABLE rep_groups_added_daily;";
+	$sql[]="CREATE TABLE rep_groups_added_daily (
+	day int not null primary key,
+	added int not null default 0);";
+
+	$sql[]="DROP TABLE rep_groups_added_weekly";
+	$sql[]="CREATE TABLE rep_groups_added_weekly (
+	week int not null primary key,
+	added int not null default 0);";
+
+	$sql[]="DROP TABLE rep_groups_added_monthly";
+	$sql[]="CREATE TABLE rep_groups_added_monthly (
+	month int not null primary key,
+	added int not null default 0);";
+
+//cumulative groups
+	$sql[]="DROP TABLE rep_groups_cum_daily";
+	$sql[]="CREATE TABLE rep_groups_cum_daily (
+	day int not null primary key,
+	total int not null default 0);";
+
+	$sql[]="DROP TABLE rep_groups_cum_weekly";
+	$sql[]="CREATE TABLE rep_groups_cum_weekly (
+	week int not null primary key,
+	total int not null default 0);";
+
+	$sql[]="DROP TABLE rep_groups_cum_monthly";
+	$sql[]="CREATE TABLE rep_groups_cum_monthly (
+	month int not null primary key,
+	total int not null default 0);";
+
+//per-user activity
+	$sql[]="DROP TABLE rep_user_act_daily";
+	$sql[]="CREATE TABLE rep_user_act_daily (
+	user_id int not null,
+	day int not null,
+	tracker_opened int not null,
+	tracker_closed int not null,
+	forum int not null,
+	docs int not null,
+	cvs_commits int not null,
+	tasks_opened int not null,
+	tasks_closed int not null,
+	PRIMARY KEY (user_id,day));";
+
+	$sql[]="DROP TABLE rep_user_act_weekly";
+	$sql[]="CREATE TABLE rep_user_act_weekly (
+	user_id int not null,
+	week int not null,
+	tracker_opened int not null,
+	tracker_closed int not null,
+	forum int not null,
+	docs int not null,
+	cvs_commits int not null,
+	tasks_opened int not null,
+	tasks_closed int not null,
+	PRIMARY KEY (user_id,week));";
+
+	$sql[]="DROP TABLE rep_user_act_monthly";
+	$sql[]="CREATE TABLE rep_user_act_monthly (
+	user_id int not null,
+	month int not null,
+	tracker_opened int not null,
+	tracker_closed int not null,
+	forum int not null,
+	docs int not null,
+	cvs_commits int not null,
+	tasks_opened int not null,
+	tasks_closed int not null,
+	PRIMARY KEY (user_id,month));";
+
+	$sql[]="DROP VIEW rep_user_act_oa_vw";
+	$sql[]="CREATE VIEW rep_user_act_oa_vw AS
+	SELECT user_id,
+	sum(tracker_opened) AS tracker_opened,
+	sum(tracker_closed) AS tracker_closed,
+	sum(forum) AS forum, 
+	sum(docs) AS docs, 
+	sum(cvs_commits) AS cvs_commits,
+	sum(tasks_opened) AS tasks_opened,
+	sum(tasks_closed) AS tasks_closed 
+	FROM rep_user_act_monthly
+	GROUP BY user_id;";
+
+//per-project activity
+	$sql[]="DROP TABLE rep_group_act_daily";
+	$sql[]="CREATE TABLE rep_group_act_daily (
+	group_id int not null,
+	day int not null,
+	tracker_opened int not null,
+	tracker_closed int not null,
+	forum int not null,
+	docs int not null,
+	downloads int not null,
+	cvs_commits int not null,
+	tasks_opened int not null,
+	tasks_closed int not null,
+	PRIMARY KEY (group_id,day));";
+
+	$sql[]="DROP INDEX repgroupactdaily_day";
+	$sql[]="CREATE INDEX repgroupactdaily_day ON rep_group_act_daily(day)";
+
+	$sql[]="DROP TABLE rep_group_act_weekly";
+	$sql[]="CREATE TABLE rep_group_act_weekly (
+	group_id int not null,
+	week int not null,
+	tracker_opened int not null,
+	tracker_closed int not null,
+	forum int not null,
+	docs int not null,
+	downloads int not null,
+	cvs_commits int not null,
+	tasks_opened int not null,
+	tasks_closed int not null,
+	PRIMARY KEY (group_id,week));";
+
+	$sql[]="DROP INDEX repgroupactweekly_week";
+	$sql[]="CREATE INDEX repgroupactweekly_week ON rep_group_act_weekly(week)";
+
+	$sql[]="DROP TABLE rep_group_act_monthly";
+	$sql[]="CREATE TABLE rep_group_act_monthly (
+	group_id int not null,
+	month int not null,
+	tracker_opened int not null,
+	tracker_closed int not null,
+	forum int not null,
+	docs int not null,
+	downloads int not null,
+	cvs_commits int not null,
+	tasks_opened int not null,
+	tasks_closed int not null,
+	PRIMARY KEY (group_id,month));";
+
+	$sql[]="DROP INDEX repgroupactmonthly_month";
+	$sql[]="CREATE INDEX repgroupactmonthly_month ON rep_group_act_monthly(month)";
+
+	$sql[]="DROP VIEW rep_group_act_oa_vw";
+	$sql[]="CREATE VIEW rep_group_act_oa_vw AS
+	SELECT group_id,
+	sum(tracker_opened) AS tracker_opened,
+	sum(tracker_closed) AS tracker_closed,
+	sum(forum) AS forum,
+	sum(docs) AS docs,
+	sum(downloads) AS downloads,
+	sum(cvs_commits) AS cvs_commits,
+	sum(tasks_opened) AS tasks_opened,
+	sum(tasks_closed) AS tasks_closed
+	FROM rep_group_act_monthly
+	GROUP BY group_id;";
+
+//overall activity
+	$sql[]="DROP VIEW rep_site_act_daily_vw";
+	$sql[]="CREATE VIEW rep_site_act_daily_vw AS 
+	SELECT day,
+	sum(tracker_opened) AS tracker_opened,
+	sum(tracker_closed) AS tracker_closed,
+	sum(forum) AS forum,
+	sum(docs) AS docs,
+	sum(downloads) AS downloads,
+	sum(cvs_commits) AS cvs_commits,
+	sum(tasks_opened) AS tasks_opened,
+	sum(tasks_closed) AS tasks_closed
+	FROM rep_group_act_daily
+	GROUP BY day;";
+
+	$sql[]="DROP VIEW rep_site_act_weekly_vw";
+	$sql[]="CREATE VIEW rep_site_act_weekly_vw AS 
+	SELECT week,
+	sum(tracker_opened) AS tracker_opened,
+	sum(tracker_closed) AS tracker_closed,
+	sum(forum) AS forum,
+	sum(docs) AS docs,
+	sum(downloads) AS downloads,
+	sum(cvs_commits) AS cvs_commits,
+	sum(tasks_opened) AS tasks_opened,
+	sum(tasks_closed) AS tasks_closed
+	FROM rep_group_act_weekly
+	GROUP BY week;";
+
+	$sql[]="DROP VIEW rep_site_act_monthly_vw";
+	$sql[]="CREATE VIEW rep_site_act_monthly_vw AS
+	SELECT month,
+	sum(tracker_opened) AS tracker_opened,
+	sum(tracker_closed) AS tracker_closed,
+	sum(forum) AS forum,
+	sum(docs) AS docs,
+	sum(downloads) AS downloads,
+	sum(cvs_commits) AS cvs_commits,
+	sum(tasks_opened) AS tasks_opened,
+	sum(tasks_closed) AS tasks_closed
+	FROM rep_group_act_monthly
+	GROUP BY month;";
+
+	$sql[]="DROP VIEW rep_site_act_oa_vw";
+	$sql[]="CREATE VIEW rep_site_act_oa_vw AS
+	sum(tracker_opened) AS tracker_opened,
+	sum(tracker_closed) AS tracker_closed,
+	sum(forum) AS forum,
+	sum(docs) AS docs,
+	sum(downloads) AS downloads,
+	sum(cvs_commits) AS cvs_commits,
+	sum(tasks_opened) AS tasks_opened,
+	sum(tasks_closed) AS tasks_closed
+	FROM rep_group_act_monthly;";
+
+	for ($i=0; $i<count($sql); $i++) {
+
+		$res=db_query($sql[$i]);
+
+	}
+
+}
+
+function initialData() {
+	if (!$this->backfill_users_added_daily()) {
+		return false;
+	}
+	if (!$this->backfill_users_added_weekly()) {
+		return false;
+	}
+	if (!$this->backfill_users_added_monthly()) {
+		return false;
+	}
+	if (!$this->backfill_users_cum_daily()) {
+		return false;
+	}
+	if (!$this->backfill_users_cum_weekly()) {
+		return false;
+	}
+	if (!$this->backfill_users_cum_monthly()) {
+		return false;
+	}
+	if (!$this->backfill_groups_added_daily()) {
+		return false;
+	}
+	if (!$this->backfill_groups_added_weekly()) {
+		return false;
+	}
+	if (!$this->backfill_groups_added_monthly()) {
+		return false;
+	}
+	if (!$this->backfill_groups_cum_daily()) {
+		return false;
+	}
+	if (!$this->backfill_groups_cum_weekly()) {
+		return false;
+	}
+	if (!$this->backfill_groups_cum_monthly()) {
+		return false;
+	}
+	if (!$this->backfill_user_act_daily()) {
+		return false;
+	}
+	if (!$this->backfill_user_act_weekly()) {
+		return false;
+	}
+	if (!$this->backfill_user_act_monthly()) {
+		return false;
+	}
+	if (!$this->backfill_group_act_daily()) {
+		return false;
+	}
+	if (!$this->backfill_group_act_weekly()) {
+		return false;
+	}
+	if (!$this->backfill_group_act_monthly()) {
+		return false;
+	}
+	return true;
+
+}
+
+function dailyData() {
+	if (!$this->backfill_users_added_daily(1)) {
+		return false;
+	}
+	if (!$this->backfill_users_added_weekly(1)) {
+		return false;
+	}
+	if (!$this->backfill_users_added_monthly(2)) {
+		return false;
+	}
+	if (!$this->backfill_users_cum_daily(1)) {
+		return false;
+	}
+	if (!$this->backfill_users_cum_weekly(1)) {
+		return false;
+	}
+	if (!$this->backfill_users_cum_monthly(2)) {
+		return false;
+	}
+	if (!$this->backfill_user_act_daily(1)) {
+		return false;
+	}
+	if (!$this->backfill_user_act_weekly(1)) {
+		return false;
+	}
+	if (!$this->backfill_user_act_monthly(2)) {
+		return false;
+	}
+	if (!$this->backfill_group_act_daily(1)) {
+		return false;
+	}
+	if (!$this->backfill_group_act_weekly(1)) {
+		return false;
+	}
+	if (!$this->backfill_group_act_monthly(2)) {
+		return false;
+	}
+	return true;
+}
+/**
+ *	Add a row to the users_added_daily report table.
+ *
+ *	@param	int	Day - the unix time of the beginning of the day.
+ *	@return	boolean	Success.
+ */
+function users_added_daily($day) {
+	db_query("DELETE FROM rep_users_added_daily WHERE day='$day'");
+
+	$sql="INSERT INTO rep_users_added_daily (day,added) 
+		VALUES ('$day',(SELECT count(*) FROM users WHERE status='A' AND add_date 
+		BETWEEN '$day' AND '". ($day + REPORT_DAY_SPAN - 1) ."' ))";
+	return db_query($sql);
+}
+
+/**
+ *	Populate the users_added_daily report table.
+ *
+ *	@return	boolean	Success.
+ */
+function backfill_users_added_daily($count=10000) {
+	$today=mktime(0,0,0,date('m'),date('d')-1,date('Y'));
+	if (!$start_date=$this->getMinDate()) {
+		$this->setError('backfill_users_added_daily:: Could Not Get Start Date');
+		return false;
+	}
+	while (true) {
+		$day=($today-($i*REPORT_DAY_SPAN));
+		if (!$this->users_added_daily($day)) {
+			$this->setError('backfill_users_added_daily:: Error adding daily row: '.db_error());
+			return false;
+		}
+		if ($day < $start_date) {
+			break;
+		}
+		$i++;
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+/**
+ *	Add a row to the groups_added_daily report table.
+ *
+ *	@param	int	Day - the unix time of the beginning of the day.
+ *	@return	boolean	Success.
+ */
+function groups_added_daily($day) {
+	db_query("DELETE FROM rep_groups_added_daily WHERE day='$day'");
+
+	$sql="INSERT INTO rep_groups_added_daily (day,added) 
+		VALUES ('$day',(SELECT count(*) FROM groups WHERE status='A' AND register_time 
+		BETWEEN '$day' AND '". ($day + REPORT_DAY_SPAN - 1) ."' ))";
+	return db_query($sql);
+}
+
+/**
+ *	Populate the groups_added_daily report table.
+ *
+ *	@return	boolean	Success.
+ */
+function backfill_groups_added_daily($count=10000) {
+	$today=mktime(0,0,0,date('m'),date('d')-1,date('Y'));
+	if (!$start_date=$this->getMinDate()) {
+		$this->setError('backfill_groups_added_daily:: Could Not Get Start Date');
+		return false;
+	}
+	while (true) {
+		$day=($today-($i*REPORT_DAY_SPAN));
+		if (!$this->groups_added_daily($day)) {
+			$this->setError('backfill_groups_added_daily:: Error adding daily row: '.db_error());
+			return false;
+		}
+		if ($day < $start_date) {
+			break;
+		}
+		$i++;
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+/**
+ *  Add a row to the users_added_weekly report table.
+ *
+ *  @param  int Week - the unix time of the beginning of the sunday for this week.
+ *  @return boolean Success.
+ */
+function users_added_weekly($week) {
+	db_query("DELETE FROM rep_users_added_weekly WHERE week='$week'");
+
+	$sql="INSERT INTO rep_users_added_weekly (week,added)
+		VALUES ('$week',(SELECT count(*) FROM users WHERE status='A' AND add_date
+		BETWEEN '$week' AND '". ($week+REPORT_WEEK_SPAN-1) ."' ))";
+	return db_query($sql);
+}
+
+/**
+ *  Populate the users_added_weekly report table.
+ *
+ *  @return boolean Success.
+ */
+function backfill_users_added_weekly($count=10000) {
+
+	$arr =& $this->getWeekStartArr();
+
+	for ($i=0; $i<count($arr); $i++) {
+		if (!$this->users_added_weekly($arr[$i])) {
+			$this->setError('backfill_users_added_weekly:: Error adding weekly row: '.db_error());
+			return false;
+		}
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+/**
+ *  Add a row to the groups_added_weekly report table.
+ *
+ *  @param  int Week - the unix time of the beginning of the sunday for this week.
+ *  @return boolean Success.
+ */
+function groups_added_weekly($week) {
+	db_query("DELETE FROM rep_groups_added_weekly WHERE week='$week'");
+
+	$sql="INSERT INTO rep_groups_added_weekly (week,added)
+		VALUES ('$week',(SELECT count(*) FROM groups WHERE status='A' AND register_time
+		BETWEEN '$week' AND '". ($week+REPORT_WEEK_SPAN-1) ."' ))";
+	return db_query($sql);
+}
+
+/**
+ *  Populate the users_added_weekly report table.
+ *
+ *  @return boolean Success.
+ */
+function backfill_groups_added_weekly($count=10000) {
+
+	$arr =& $this->getWeekStartArr();
+
+	for ($i=0; $i<count($arr); $i++) {
+		if (!$this->groups_added_weekly($arr[$i])) {
+			$this->setError('backfill_groups_added_weekly:: Error adding weekly row: '.db_error());
+			return false;
+		}
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+/**
+ *  Add a row to the users_added_monthly report table.
+ *
+ *  @param  int month_start - the unix time of the beginning of the month.
+ *  @param  int month_end - the unix time of the end of the month.
+ *  @return boolean Success.
+ */
+function users_added_monthly($month,$end) {
+	db_query("DELETE FROM rep_users_added_monthly WHERE month='$month'");
+
+	$sql="INSERT INTO rep_users_added_monthly (month,added)
+		VALUES ('$month',(SELECT count(*) FROM users WHERE status='A' AND add_date
+		BETWEEN '$month' AND '$end' ))";
+	return db_query($sql);
+}
+
+/**
+ *  Populate the users_added_monthly report table.
+ *
+ *  @return boolean Success.
+ */
+function backfill_users_added_monthly($count=10000) {
+
+	$arr =& $this->getMonthStartArr();
+
+//skipping first one
+	for ($i=1; $i<count($arr); $i++) {
+		if (!$this->users_added_monthly($arr[$i],($arr[$i-1]-1))) {
+			$this->setError('backfill_users_added_monthly:: Error adding monthly row: '.db_error());
+			return false;
+		}
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+/**
+ *  Add a row to the groups_added_monthly report table.
+ *
+ *  @param  int month_start - the unix time of the beginning of the month.
+ *  @param  int month_end - the unix time of the end of the month.
+ *  @return boolean Success.
+ */
+function groups_added_monthly($month,$end) {
+	db_query("DELETE FROM rep_groups_added_monthly WHERE month='$month'");
+
+	$sql="INSERT INTO rep_groups_added_monthly (month,added)
+		VALUES ('$month',(SELECT count(*) FROM groups WHERE status='A' AND register_time
+		BETWEEN '$month' AND '$end' ))";
+	return db_query($sql);
+}
+
+/**
+ *  Populate the groups_added_monthly report table.
+ *
+ *  @return boolean Success.
+ */
+function backfill_groups_added_monthly($count=10000) {
+
+	$arr =& $this->getMonthStartArr();
+
+//skipping first one
+	for ($i=1; $i<count($arr); $i++) {
+		if (!$this->groups_added_monthly($arr[$i],($arr[$i-1]-1))) {
+			$this->setError('backfill_groups_added_monthly:: Error adding monthly row: '.db_error());
+			return false;
+		}
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+
+// ******************************
+
+
+/**
+ *	Add a row to the users_cum_daily report table.
+ *
+ *	@param	int	Day - the unix time of the beginning of the day.
+ *	@return	boolean	Success.
+ */
+function users_cum_daily($day) {
+	db_query("DELETE FROM rep_users_cum_daily WHERE day='$day'");
+
+	$sql="INSERT INTO rep_users_cum_daily (day,total) 
+		VALUES ('$day',(SELECT count(*) FROM users WHERE status='A' AND add_date 
+		BETWEEN '0' AND '$day'))";
+	return db_query($sql);
+}
+
+/**
+ *	Populate the users_cum_daily report table.
+ *
+ *	@return	boolean	Success.
+ */
+function backfill_users_cum_daily($count=10000) {
+	$today=mktime(0,0,0,date('m'),date('d')-1,date('Y'));
+	if (!$start_date=$this->getMinDate()) {
+		$this->setError('backfill_users_cum_daily:: Could Not Get Start Date');
+		return false;
+	}
+	while (true) {
+		$day=$today-($i*REPORT_DAY_SPAN);
+		if (!$this->users_cum_daily($day)) {
+			$this->setError('backfill_users_cum_daily:: Error adding daily row: '.db_error());
+			return false;
+		}
+		if ($day < $start_date) {
+			break;
+		}
+		$i++;
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+/**
+ *	Add a row to the groups_cum_daily report table.
+ *
+ *	@param	int	Day - the unix time of the beginning of the day.
+ *	@return	boolean	Success.
+ */
+function groups_cum_daily($day) {
+	db_query("DELETE FROM rep_groups_cum_daily WHERE day='$day'");
+
+	$sql="INSERT INTO rep_groups_cum_daily (day,total) 
+		VALUES ('$day',(SELECT count(*) FROM groups WHERE status='A' AND register_time 
+		BETWEEN '0' AND '$day'))";
+	return db_query($sql);
+}
+
+/**
+ *	Populate the groups_cum_daily report table.
+ *
+ *	@return	boolean	Success.
+ */
+function backfill_groups_cum_daily($count=10000) {
+	$today=mktime(0,0,0,date('m'),date('d')-1,date('Y'));
+	if (!$start_date=$this->getMinDate()) {
+		$this->setError('backfill_groups_cum_daily:: Could Not Get Start Date');
+		return false;
+	}
+	while (true) {
+		$day=$today-($i*REPORT_DAY_SPAN);
+		if (!$this->groups_cum_daily($day)) {
+			$this->setError('backfill_groups_cum_daily:: Error adding daily row: '.db_error());
+			return false;
+		}
+		if ($day < $start_date) {
+			break;
+		}
+		$i++;
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+/**
+ *  Add a row to the users_cum_weekly report table.
+ *
+ *  @param  int Week - the unix time of the beginning of the sunday for this week.
+ *  @return boolean Success.
+ */
+function users_cum_weekly($week) {
+	db_query("DELETE FROM rep_users_cum_weekly WHERE week='$week'");
+
+	$sql="INSERT INTO rep_users_cum_weekly (week,total)
+		VALUES ('$week',(SELECT count(*) FROM users WHERE status='A' AND add_date
+		BETWEEN '0' AND '". ($week+REPORT_WEEK_SPAN-1 ). "'))";
+	return db_query($sql);
+}
+
+/**
+ *  Populate the users_cum_weekly report table.
+ *
+ *  @return boolean Success.
+ */
+function backfill_users_cum_weekly($count=10000) {
+
+	$arr =& $this->getWeekStartArr();
+
+	for ($i=0; $i<count($arr); $i++) {
+		if (!$this->groups_cum_weekly($arr[$i])) {
+			$this->setError('backfill_users_cum_weekly:: Error adding weekly row: '.db_error());
+			return false;
+		}
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+/**
+ *  Add a row to the groups_cum_weekly report table.
+ *
+ *  @param  int Week - the unix time of the beginning of the sunday for this week.
+ *  @return boolean Success.
+ */
+function groups_cum_weekly($week) {
+	db_query("DELETE FROM rep_groups_cum_weekly WHERE week='$week'");
+
+	$sql="INSERT INTO rep_groups_cum_weekly (week,total)
+		VALUES ('$week',(SELECT count(*) FROM groups WHERE status='A' AND register_time
+		BETWEEN '0' AND '". ($week+REPORT_WEEK_SPAN-1 ). "'))";
+	return db_query($sql);
+}
+
+/**
+ *  Populate the groups_cum_weekly report table.
+ *
+ *  @return boolean Success.
+ */
+function backfill_groups_cum_weekly($count=10000) {
+
+	$arr =& $this->getWeekStartArr();
+
+	for ($i=0; $i<count($arr); $i++) {
+		if (!$this->users_cum_weekly($arr[$i])) {
+			$this->setError('backfill_groups_cum_weekly:: Error adding weekly row: '.db_error());
+			return false;
+		}
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+/**
+ *  Add a row to the users_cum_monthly report table.
+ *
+ *  @param  int month_start - the unix time of the beginning of the month.
+ *  @param  int month_end - the unix time of the end of the month.
+ *  @return boolean Success.
+ */
+function users_cum_monthly($month,$end) {
+	db_query("DELETE FROM rep_users_cum_monthly WHERE month='$month'");
+
+	$sql="INSERT INTO rep_users_cum_monthly (month,total)
+		VALUES ('$month',(SELECT count(*) FROM users WHERE status='A' AND add_date
+		BETWEEN '0' AND '$end'))";
+	return db_query($sql);
+}
+
+/**
+ *  Populate the users_cum_monthly report table.
+ *
+ *  @return boolean Success.
+ */
+function backfill_users_cum_monthly($count=10000) {
+
+	$arr =& $this->getMonthStartArr();
+
+//skip first one
+	for ($i=1; $i<count($arr); $i++) {
+		if (!$this->users_cum_monthly($arr[$i],($arr[$i-1]-1))) {
+			$this->setError('backfill_users_cum_monthly:: Error adding monthly row: '.db_error());
+			return false;
+		}
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+/**
+ *  Add a row to the groups_cum_monthly report table.
+ *
+ *  @param  int month_start - the unix time of the beginning of the month.
+ *  @param  int month_end - the unix time of the end of the month.
+ *  @return boolean Success.
+ */
+function groups_cum_monthly($month,$end) {
+	db_query("DELETE FROM rep_groups_cum_monthly WHERE month='$month'");
+
+	$sql="INSERT INTO rep_groups_cum_monthly (month,total)
+		VALUES ('$month',(SELECT count(*) FROM groups WHERE status='A' AND register_time
+		BETWEEN '0' AND '$end'))";
+	return db_query($sql);
+}
+
+/**
+ *  Populate the groups_cum_monthly report table.
+ *
+ *  @return boolean Success.
+ */
+function backfill_groups_cum_monthly($count=10000) {
+
+	$arr =& $this->getMonthStartArr();
+
+//skip first one
+	for ($i=1; $i<count($arr); $i++) {
+		if (!$this->groups_cum_monthly($arr[$i],($arr[$i-1]-1))) {
+			$this->setError('backfill_groups_cum_monthly:: Error adding monthly row: '.db_error());
+			return false;
+		}
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+
+// ************************
+
+
+/**
+ *	Add a row to the user_act_daily report table.
+ *
+ *	@param	int	Day - the unix time of the beginning of the day.
+ *	@return	boolean	Success.
+ */
+function user_act_daily($day) {
+
+	db_query("DELETE FROM rep_user_act_daily WHERE day='$day'");
+
+$sql="INSERT INTO rep_user_act_daily
+SELECT user_id,day,coalesce(tracker_opened,0) AS tracker_opened,
+	coalesce(tracker_closed,0) AS tracker_closed,
+	coalesce(forum,0) AS forum,
+	coalesce(docs,0) AS docs,
+	coalesce(cvs_commits,0) AS cvs_commits,
+	coalesce(tasks_opened,0) AS tasks_opened,
+	coalesce(tasks_closed,0) AS tasks_closed
+	FROM
+(SELECT * FROM
+(SELECT * FROM
+(SELECT * FROM
+(SELECT * FROM
+(SELECT * FROM
+(SELECT * FROM 
+	(SELECT submitted_by AS user_id, '$day'::int AS day, count(*) AS tracker_opened
+	FROM artifact
+	WHERE open_date BETWEEN '$day' AND '". ($day+REPORT_DAY_SPAN-1) ."'
+	GROUP BY user_id,day) aopen 
+
+FULL OUTER JOIN 
+	(SELECT assigned_to AS user_id, '$day'::int AS day, count(*) AS tracker_closed
+	FROM artifact
+	WHERE close_date BETWEEN '$day' AND '". ($day+REPORT_DAY_SPAN-1) ."'
+	GROUP BY user_id,day ) aclosed USING (user_id,day)) foo1
+
+FULL OUTER JOIN 
+	(SELECT posted_by AS user_id, '$day'::int AS day, count(*) AS forum
+	FROM forum
+	WHERE post_date BETWEEN '$day' AND '". ($day+REPORT_DAY_SPAN-1) ."'
+	GROUP BY user_id,day ) forum USING (user_id,day)) foo2
+
+FULL OUTER JOIN
+	(SELECT created_by AS user_id, '$day'::int AS day, count(*) AS docs
+	FROM doc_data
+	WHERE createdate BETWEEN '$day' AND '". ($day+REPORT_DAY_SPAN-1) ."' 
+	GROUP BY user_id,day ) docs USING (user_id,day)) foo3
+
+FULL OUTER JOIN
+	(SELECT user_id,$day AS day, sum(commits) AS cvs_commits
+	FROM stats_cvs_user
+	WHERE month='". date('Ym') ."' AND day='". date('d') ."'
+	GROUP BY user_id,day ) cvs USING (user_id,day)) foo4
+
+FULL OUTER JOIN
+	(SELECT created_by AS user_id, '$day'::int AS day, count(*) AS tasks_opened
+	FROM project_task
+	WHERE start_date BETWEEN '$day' AND '". ($day+REPORT_DAY_SPAN-1) ."'
+	GROUP BY user_id,day ) topen USING (user_id,day)) foo5
+
+FULL OUTER JOIN
+	(SELECT mod_by AS user_id, '$day'::int AS day, count(*) AS tasks_closed 
+	FROM project_history
+	WHERE mod_date BETWEEN '$day' AND '". ($day+REPORT_DAY_SPAN-1) ."'
+	AND old_value='1' AND field_name='status_id'
+	GROUP BY user_id,day ) tclosed USING (user_id,day)) foo6";
+
+	return db_query($sql);
+
+}
+
+/**
+ *	Populate the user_act_daily report table.
+ *
+ *	@return	boolean	Success.
+ */
+function backfill_user_act_daily($count=10000) {
+	$today=mktime(0,0,0,date('m'),date('d')-1,date('Y'));
+	if (!$start_date=$this->getMinDate()) {
+		$this->setError('backfill_user_act_daily:: Could Not Get Start Date');
+		return false;
+	}
+	while (true) {
+		$day=$today-($i*REPORT_DAY_SPAN);
+		if (!$this->user_act_daily($day)) {
+			$this->setError('backfill_user_act_daily:: Error adding daily row: '.db_error());
+			return false;
+		}
+		if ($day < $start_date) {
+			break;
+		}
+		$i++;
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+/**
+ *  Add a row to the user_act_weekly report table.
+ *
+ *  @param  int Week - the unix time of the beginning of the sunday for this week.
+ *  @return boolean Success.
+ */
+function user_act_weekly($week) {
+	db_query("DELETE FROM rep_user_act_weekly WHERE week='$week'");
+
+	$sql="INSERT INTO rep_user_act_weekly (user_id,week,tracker_opened,tracker_closed,
+		forum,docs,cvs_commits,tasks_opened,tasks_closed)
+		SELECT user_id,'$week'::int AS week, sum(tracker_opened) AS tracker_opened,
+		sum(tracker_closed) AS tracker_closed,
+		sum(forum) AS forum,
+		sum(docs) AS docs,
+		sum(cvs_commits) AS cvs_commits,
+		sum(tasks_opened) AS tasks_opened,
+		sum(tasks_closed) AS tasks_closed
+		FROM rep_user_act_daily
+		WHERE DAY
+		BETWEEN '$week' AND '". ($week+REPORT_WEEK_SPAN-1) ."'
+		GROUP BY user_id,week";
+	return db_query($sql);
+}
+
+/**
+ *  Populate the user_act_weekly report table.
+ *
+ *  @return boolean Success.
+ */
+function backfill_user_act_weekly($count=10000) {
+
+	$arr =& $this->getWeekStartArr();
+
+	for ($i=0; $i<count($arr); $i++) {
+		if (!$this->user_act_weekly($arr[$i])) {
+			$this->setError('backfill_user_act_weekly:: Error adding weekly row: '.db_error());
+			return false;
+		}
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+/**
+ *  Add a row to the user_act_monthly report table.
+ *
+ *  @param  int month_start - the unix time of the beginning of the month.
+ *  @param  int month_end - the unix time of the end of the month.
+ *  @return boolean Success.
+ */
+function user_act_monthly($month,$end) {
+	db_query("DELETE FROM rep_user_act_monthly WHERE month='$month'");
+
+	$sql="INSERT INTO rep_user_act_monthly (user_id,month,tracker_opened,tracker_closed,
+		forum,docs,cvs_commits,tasks_opened,tasks_closed)
+		SELECT user_id,'$month'::int AS month, sum(tracker_opened) AS tracker_opened,
+		sum(tracker_closed) AS tracker_closed,
+		sum(forum) AS forum,
+		sum(docs) AS docs,
+		sum(cvs_commits) AS cvs_commits,
+		sum(tasks_opened) AS tasks_opened,
+		sum(tasks_closed) AS tasks_closed
+		FROM rep_user_act_daily
+		WHERE DAY
+		BETWEEN '$month' AND '$end'
+		GROUP BY user_id,month";
+	return db_query($sql);
+}
+
+/**
+ *  Populate the user_act_monthly report table.
+ *
+ *  @return boolean Success.
+ */
+function backfill_user_act_monthly($count=10000) {
+
+	$arr =& $this->getMonthStartArr();
+
+	for ($i=1; $i<count($arr); $i++) {
+		if (!$this->user_act_monthly($arr[$i],($arr[$i-1]-1))) {
+			$this->setError('backfill_user_act_monthly:: Error adding monthly row: '.db_error());
+			return false;
+		}
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+// ************************
+
+
+/**
+ *	Add a row to the group_act_daily report table.
+ *
+ *	@param	int	Day - the unix time of the beginning of the day.
+ *	@return	boolean	Success.
+ */
+function group_act_daily($day) {
+
+	db_query("DELETE FROM rep_group_act_daily WHERE day='$day'");
+
+$sql="INSERT INTO rep_group_act_daily
+SELECT group_id,day,coalesce(tracker_opened,0) AS tracker_opened,
+	coalesce(tracker_closed,0) AS tracker_closed,
+	coalesce(forum,0) AS forum,
+	coalesce(docs,0) AS docs,
+	coalesce(downloads,0) AS downloads,
+	coalesce(cvs_commits,0) AS cvs_commits,
+	coalesce(tasks_opened,0) AS tasks_opened,
+	coalesce(tasks_closed,0) AS tasks_closed
+	FROM
+(SELECT * FROM
+(SELECT * FROM
+(SELECT * FROM
+(SELECT * FROM
+(SELECT * FROM
+(SELECT * FROM
+(SELECT * FROM 
+	(SELECT agl.group_id, '$day'::int AS day, count(*) AS tracker_opened
+	FROM artifact a, artifact_group_list agl
+	WHERE a.open_date BETWEEN '$day' AND '". ($day+REPORT_DAY_SPAN-1) ."'
+	AND a.group_artifact_id=agl.group_artifact_id
+	GROUP BY group_id,day) aopen 
+
+FULL OUTER JOIN 
+	(SELECT agl.group_id, '$day'::int AS day, count(*) AS tracker_closed
+	FROM artifact a, artifact_group_list agl
+	WHERE a.close_date BETWEEN '$day' AND '". ($day+REPORT_DAY_SPAN-1) ."'
+	AND a.group_artifact_id=agl.group_artifact_id
+	GROUP BY group_id,day ) aclosed USING (group_id,day)) foo1
+
+FULL OUTER JOIN 
+	(SELECT fgl.group_id, '$day'::int AS day, count(*) AS forum
+	FROM forum f, forum_group_list fgl
+	WHERE f.post_date BETWEEN '$day' AND '". ($day+REPORT_DAY_SPAN-1) ."'
+	AND f.group_forum_id=fgl.group_forum_id
+	GROUP BY group_id,day ) forum USING (group_id,day)) foo2
+
+FULL OUTER JOIN
+	(SELECT group_id, '$day'::int AS day, count(*) AS docs
+	FROM doc_data
+	WHERE createdate BETWEEN '$day' AND '". ($day+REPORT_DAY_SPAN-1) ."' 
+	GROUP BY group_id,day ) docs USING (group_id,day)) foo3
+
+FULL OUTER JOIN
+	(SELECT fp.group_id, '$day'::int AS day, count(*) AS downloads
+	FROM frs_package fp, frs_release fr, frs_file ff, frs_dlstats_file fdf
+	WHERE fp.package_id=fr.package_id
+	AND fr.release_id=ff.release_id
+	AND ff.file_id=fdf.file_id
+	AND fdf.month = '". date('Ym',$day) ."' AND fdf.day = '". date('d',$day) ."'
+	GROUP BY fp.group_id,day ) docs USING (group_id,day)) foo4
+
+FULL OUTER JOIN
+	(SELECT group_id,$day AS day, sum(commits) AS cvs_commits
+	FROM stats_cvs_group
+	WHERE month='". date('Ym',$day) ."' AND day='". date('d',$day) ."'
+	GROUP BY group_id,day ) cvs USING (group_id,day)) foo5
+
+FULL OUTER JOIN
+	(SELECT pgl.group_id, '$day'::int AS day,count(*) AS tasks_opened
+	FROM project_task pt, project_group_list pgl
+	WHERE pt.start_date BETWEEN '$day' AND '". ($day+REPORT_DAY_SPAN-1) ."'
+	AND pt.group_project_id=pgl.group_project_id
+	GROUP BY group_id,day ) topen USING (group_id,day)) foo6
+
+FULL OUTER JOIN
+	(SELECT pgl.group_id, '$day'::int AS day, count(*) AS tasks_closed 
+	FROM project_history ph, project_task pt, project_group_list pgl
+	WHERE ph.mod_date BETWEEN '$day' AND '". ($day+REPORT_DAY_SPAN-1) ."'
+	AND ph.old_value='1' 
+	AND ph.field_name='status_id'
+	AND ph.project_task_id=pt.project_task_id
+	AND pt.group_project_id=pgl.group_project_id
+	GROUP BY group_id,day ) tclosed USING (group_id,day)) foo7";
+
+	return db_query($sql);
+
+}
+
+/**
+ *	Populate the group_act_daily report table.
+ *
+ *	@return	boolean	Success.
+ */
+function backfill_group_act_daily($count=10000) {
+	$today=mktime(0,0,0,date('m'),date('d')-1,date('Y'));
+	if (!$start_date=$this->getMinDate()) {
+		$this->setError('backfill_group_act_daily:: Could Not Get Start Date');
+		return false;
+	}
+	while (true) {
+		$day=$today-($i*REPORT_DAY_SPAN);
+		if (!$this->group_act_daily($day)) {
+			$this->setError('backfill_group_act_daily:: Error adding daily row: '.db_error());
+			return false;
+		}
+		if ($day < $start_date) {
+			break;
+		}
+		$i++;
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+/**
+ *  Add a row to the group_act_weekly report table.
+ *
+ *  @param  int Week - the unix time of the beginning of the sunday for this week.
+ *  @return boolean Success.
+ */
+function group_act_weekly($week) {
+	db_query("DELETE FROM rep_group_act_weekly WHERE week='$week'");
+
+	$sql="INSERT INTO rep_group_act_weekly (group_id,week,tracker_opened,tracker_closed,
+		forum,docs,downloads,cvs_commits,tasks_opened,tasks_closed)
+		SELECT group_id,'$week'::int AS week, sum(tracker_opened) AS tracker_opened,
+		sum(tracker_closed) AS tracker_closed,
+		sum(forum) AS forum,
+		sum(docs) AS docs,
+		sum(downloads) AS downloads,
+		sum(cvs_commits) AS cvs_commits,
+		sum(tasks_opened) AS tasks_opened,
+		sum(tasks_closed) AS tasks_closed
+		FROM rep_group_act_daily
+		WHERE DAY
+		BETWEEN '$week' AND '". ($week+REPORT_WEEK_SPAN-1) ."'
+		GROUP BY group_id,week";
+	return db_query($sql);
+}
+
+/**
+ *  Populate the group_act_weekly report table.
+ *
+ *  @return boolean Success.
+ */
+function backfill_group_act_weekly($count=10000) {
+
+	$arr =& $this->getWeekStartArr();
+
+	for ($i=0; $i<count($arr); $i++) {
+		if (!$this->group_act_weekly($arr[$i])) {
+			$this->setError('backfill_user_act_weekly:: Error adding weekly row: '.db_error());
+			return false;
+		}
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+/**
+ *  Add a row to the group_act_monthly report table.
+ *
+ *  @param  int month_start - the unix time of the beginning of the month.
+ *  @param  int month_end - the unix time of the end of the month.
+ *  @return boolean Success.
+ */
+function group_act_monthly($month,$end) {
+	db_query("DELETE FROM rep_group_act_monthly WHERE month='$month'");
+
+	$sql="INSERT INTO rep_group_act_monthly (group_id,month,tracker_opened,tracker_closed,
+		forum,docs,downloads,cvs_commits,tasks_opened,tasks_closed)
+		SELECT group_id,'$month'::int AS month, sum(tracker_opened) AS tracker_opened,
+		sum(tracker_closed) AS tracker_closed,
+		sum(forum) AS forum,
+		sum(docs) AS docs,
+		sum(downloads) AS downloads,
+		sum(cvs_commits) AS cvs_commits,
+		sum(tasks_opened) AS tasks_opened,
+		sum(tasks_closed) AS tasks_closed
+		FROM rep_group_act_daily
+		WHERE DAY
+		BETWEEN '$month' AND '$end'
+		GROUP BY group_id,month";
+	return db_query($sql);
+}
+
+/**
+ *  Populate the group_act_monthly report table.
+ *
+ *  @return boolean Success.
+ */
+function backfill_group_act_monthly($count=10000) {
+
+	$arr =& $this->getMonthStartArr();
+
+	for ($i=1; $i<count($arr); $i++) {
+		if (!$this->group_act_monthly($arr[$i],($arr[$i-1]-1))) {
+			$this->setError('backfill_group_act_monthly:: Error adding monthly row: '.db_error());
+			return false;
+		}
+		if ($i >= $count) {
+			break;
+		}
+	}
+	return true;
+}
+
+/**
+ *  Add a row to the rep_time_category table.
+ *
+ *	@param	string	The category name.
+ *  @return boolean Success.
+ */
+function addTimeCode($category_name) {
+	return db_query("INSERT INTO rep_time_category (category_name) VALUES ('$category_name')");
+}
+
+/**
+ *  Update the rep_time_category table.
+ *
+ *	@param	string	The category name.
+ *  @return boolean Success.
+ */
+function updateTimeCode($time_code, $category_name) {
+	return db_query("UPDATE rep_time_category SET category_name='$category_name' WHERE time_code='$time_code'");
+}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/reporting/ReportSiteAct.class
===================================================================
--- trunk/gforge_base/gforge/common/reporting/ReportSiteAct.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/reporting/ReportSiteAct.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,110 @@
+<?php
+/**
+ * Reporting System
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: ReportSiteAct.class 3197 2004-07-28 17:34:45Z tperdue $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2003-03-16
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/reporting/Report.class');
+
+class ReportSiteAct extends Report {
+
+var $res;
+
+function ReportSiteAct($span,$start=0,$end=0) {
+	$this->Report();
+
+	if (!$start) {
+		$start=mktime(0,0,0,date('m'),1,date('Y'));;
+	}
+	if (!$end) {
+		$end=time();
+	} else {
+		$end--;
+	}
+
+	if (!$span || $span == REPORT_TYPE_MONTHLY) {
+
+		$res=db_query("SELECT * FROM rep_site_act_monthly_vw 
+			WHERE month BETWEEN '$start' AND '$end' ORDER BY month");
+
+	} elseif ($span == REPORT_TYPE_WEEKLY) {
+
+		$res=db_query("SELECT * FROM rep_site_act_weekly_vw 
+			WHERE week BETWEEN '$start' AND '$end' ORDER BY week");
+
+	} elseif ($span == REPORT_TYPE_DAILY) {
+
+		$res=db_query("SELECT * FROM rep_site_act_daily_vw 
+			WHERE day BETWEEN '$start' AND '$end' ORDER BY day ASC");
+
+	}
+
+	$this->start_date=$start;
+	$this->end_date=$end;
+
+	if (!$res || db_error()) {
+		$this->setError('ReportProjectAct:: '.db_error());
+		return false;
+	}
+	$this->setSpan($span);
+	$this->setDates($res,0);
+	$this->res=$res;
+	return true;
+}
+
+function &getTrackerOpened() {
+	return util_result_column_to_array($this->res,1);
+}
+
+function &getTrackerClosed() {
+	return util_result_column_to_array($this->res,2);
+}
+
+function &getForum() {
+	return util_result_column_to_array($this->res,3);
+}
+
+function &getDocs() {
+	return util_result_column_to_array($this->res,4);
+}
+
+function &getDownloads() {
+	return util_result_column_to_array($this->res,5);
+}
+
+function &getCVSCommits() {
+	return util_result_column_to_array($this->res,6);
+}
+
+function &getTaskOpened() {
+	return util_result_column_to_array($this->res,7);
+}
+
+function &getTaskClosed() {
+	return util_result_column_to_array($this->res,8);
+}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/reporting/ReportSiteTime.class
===================================================================
--- trunk/gforge_base/gforge/common/reporting/ReportSiteTime.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/reporting/ReportSiteTime.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Reporting System
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: ReportSiteTime.class 3197 2004-07-28 17:34:45Z tperdue $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2003-03-16
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/reporting/Report.class');
+
+class ReportSiteTime extends Report {
+
+function ReportSiteTime($type,$start=0,$end=0) {
+	$this->Report();
+
+	if (!$start) {
+		$start=mktime(0,0,0,date('m'),1,date('Y'));;
+	}
+	if (!$end) {
+		$end=time();
+	} else {
+		$end--;
+	}
+
+	//
+	//	Task report
+	//
+	if (!$type || $type=='tasks') {
+
+		$res=db_query("SELECT pt.summary,sum(rtt.hours) AS hours 
+			FROM rep_time_tracking rtt, project_task pt, project_group_list pgl
+			WHERE pgl.group_project_id=pt.group_project_id
+			AND rtt.report_date BETWEEN '$start' AND '$end' 
+			AND rtt.project_task_id=pt.project_task_id
+			GROUP BY pt.summary
+			ORDER BY hours DESC");
+
+	//
+	//	Category report
+	//
+	} elseif ($type=='category') {
+
+		$res=db_query("SELECT rtc.category_name, sum(rtt.hours) AS hours 
+			FROM rep_time_tracking rtt, rep_time_category rtc
+			WHERE rtt.report_date BETWEEN '$start' AND '$end' 
+			AND rtt.time_code=rtc.time_code
+			GROUP BY rtc.category_name
+			ORDER BY hours DESC");
+
+	//
+	//	Percentage this user spent on a specific subproject
+	//
+	} elseif ($type=='subproject') {
+
+		$res=db_query("SELECT pgl.project_name, sum(rtt.hours) AS hours 
+			FROM rep_time_tracking rtt, project_task pt, project_group_list pgl
+			WHERE rtt.report_date BETWEEN '$start' AND '$end' 
+			AND rtt.project_task_id=pt.project_task_id
+			AND pt.group_project_id=pgl.group_project_id
+			GROUP BY pgl.project_name
+			ORDER BY hours DESC");
+
+	} else {
+
+		$res=db_query("SELECT u.realname, sum(rtt.hours) AS hours 
+			FROM users u, rep_time_tracking rtt, project_task pt, project_group_list pgl
+			WHERE rtt.report_date BETWEEN '$start' AND '$end' 
+			AND u.user_id=rtt.user_id
+			GROUP BY u.realname
+			ORDER BY hours DESC");
+
+	}
+
+	$this->start_date=$start;
+	$this->end_date=$end;
+
+	if (!$res || db_error()) {
+		$this->setError('ReportUserAct:: '.db_error());
+		return false;
+	}
+
+	$this->labels = util_result_column_to_array($res,0);
+	$this->setData($res,1);
+	return true;
+}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/reporting/ReportTrackerAct.class
===================================================================
--- trunk/gforge_base/gforge/common/reporting/ReportTrackerAct.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/reporting/ReportTrackerAct.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,149 @@
+<?php
+/**
+ * Reporting System
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: ReportTrackerAct.class 3197 2004-07-28 17:34:45Z tperdue $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2003-03-16
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/reporting/Report.class');
+
+class ReportTrackerAct extends Report {
+
+var $res;
+var $avgtime;
+var $opencount;
+var $stillopencount;
+
+function ReportTrackerAct($span,$group_id,$atid,$start=0,$end=0) {
+	$this->Report();
+
+	if (!$start) {
+		$start=mktime(0,0,0,date('m'),1,date('Y')-1);
+	}
+	if (!$end) {
+		$end=time();
+	} else {
+		$end--;
+	}
+
+	if (!$group_id) {
+		$this->setError('No group_id');
+		return false;
+	}
+	if (!$span || $span == REPORT_TYPE_MONTHLY) {
+
+		$arr =& $this->getMonthStartArr();
+
+		for ($i=1; $i<count($arr); $i++) {
+			if ($arr[$i]<$start || $arr[$i]>$end) {
+				//skip this month as it's not in the range
+			} else {
+
+				$this->labels[]=date('M d',$arr[$i]).' <-> '.date('M d',($arr[$i-1]-1));
+				$this->avgtime[]=$this->getAverageTime($atid,$arr[$i],($arr[$i-1]-1));
+				$this->opencount[]=$this->getOpenCount($atid,$arr[$i],($arr[$i-1]-1));
+				$this->stillopencount[]=$this->getStillOpenCount($atid,$arr[$i],($arr[$i-1]-1));
+
+			}
+		}
+
+	} elseif ($span == REPORT_TYPE_WEEKLY) {
+
+		$arr =& $this->getWeekStartArr();
+
+		for ($i=1; $i<count($arr); $i++) {
+			if ($arr[$i]<$start || $arr[$i]>$end) {
+				//skip this month as it's not in the range
+			} else {
+
+				$this->labels[]=date('M d',$arr[$i]).' <-> '.date('M d',($arr[$i-1]-1));
+				$this->avgtime[]=$this->getAverageTime($atid,$arr[$i],($arr[$i-1]-1));
+				$this->opencount[]=$this->getOpenCount($atid,$arr[$i],($arr[$i-1]-1));
+				$this->stillopencount[]=$this->getStillOpenCount($atid,$arr[$i],($arr[$i-1]-1));
+
+			}
+		}
+
+	}
+
+	$this->start_date=$start;
+	$this->end_date=$end;
+
+	$this->setSpan($span);
+	$this->setDates($res,1);
+	$this->res=$res;
+	return true;
+}
+
+function getAverageTime($atid,$start,$end) {
+	$sql="SELECT avg((close_date-open_date)/(24*60*60)) AS avgtime
+		FROM artifact
+		WHERE group_artifact_id='$atid'
+		AND close_date > 0
+		AND (open_date >= '$start' AND open_date <= '$end')";
+	$res=db_query($sql);
+echo db_error();
+	return db_result($res,0,0);
+}
+
+function getOpenCount($atid,$start,$end) {
+	$sql="SELECT count(*)
+		FROM artifact
+		WHERE 
+		group_artifact_id='$atid'
+		AND open_date >= '$start'
+		AND open_date <= '$end'";
+
+	$res=db_query($sql);
+echo db_error();
+	return db_result($res,0,0);
+}
+
+function getStillOpenCount($atid,$start,$end) {
+	$sql="SELECT count(*)
+		FROM artifact
+		WHERE 
+		group_artifact_id='$atid'
+		AND open_date <= '$end'
+		AND (close_date >= '$end' OR close_date < 1 OR close_date is null)";
+
+	$res=db_query($sql);
+echo db_error();
+	return db_result($res,0,0);
+}
+
+function &getAverageTimeData() {
+	return $this->avgtime;
+}
+
+function &getOpenCountData() {
+	return $this->opencount;
+}
+
+function &getStillOpenCountData() {
+	return $this->stillopencount;
+}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/reporting/ReportUserAct.class
===================================================================
--- trunk/gforge_base/gforge/common/reporting/ReportUserAct.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/reporting/ReportUserAct.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,110 @@
+<?php
+/**
+ * Reporting System
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: ReportUserAct.class 3197 2004-07-28 17:34:45Z tperdue $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2003-03-16
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/reporting/Report.class');
+
+class ReportUserAct extends Report {
+
+var $res;
+
+function ReportUserAct($span,$user_id,$start=0,$end=0) {
+	$this->Report();
+
+	if (!$start) {
+		$start=mktime(0,0,0,date('m'),1,date('Y'));;
+	}
+	if (!$end) {
+		$end=time();
+	} else {
+		$end--;
+	}
+
+	if (!$user_id) {
+		$this->setError('No User_id');
+		return false;
+	}
+	if (!$span || $span == REPORT_TYPE_MONTHLY) {
+
+		$res=db_query("SELECT * FROM rep_user_act_monthly 
+			WHERE user_id='$user_id' AND month BETWEEN '$start' AND '$end' ORDER BY month");
+
+	} elseif ($span == REPORT_TYPE_WEEKLY) {
+
+		$res=db_query("SELECT * FROM rep_user_act_weekly 
+			WHERE user_id='$user_id' AND week BETWEEN '$start' AND '$end' ORDER BY week");
+
+	} elseif ($span == REPORT_TYPE_DAILY) {
+
+		$res=db_query("SELECT * FROM rep_user_act_daily 
+			WHERE user_id='$user_id' AND day BETWEEN '$start' AND '$end' ORDER BY day ASC");
+
+	}
+
+	$this->start_date=$start;
+	$this->end_date=$end;
+
+	if (!$res || db_error()) {
+		$this->setError('ReportUserAct:: '.db_error());
+		return false;
+	}
+	$this->setSpan($span);
+	$this->setDates($res,1);
+	$this->res=$res;
+	return true;
+}
+
+function &getTrackerOpened() {
+	return util_result_column_to_array($this->res,2);
+}
+
+function &getTrackerClosed() {
+	return util_result_column_to_array($this->res,3);
+}
+
+function &getForum() {
+	return util_result_column_to_array($this->res,4);
+}
+
+function &getDocs() {
+	return util_result_column_to_array($this->res,5);
+}
+
+function &getCVSCommits() {
+	return util_result_column_to_array($this->res,6);
+}
+
+function &getTaskOpened() {
+	return util_result_column_to_array($this->res,7);
+}
+
+function &getTaskClosed() {
+	return util_result_column_to_array($this->res,8);
+}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/reporting/ReportUserAdded.class
===================================================================
--- trunk/gforge_base/gforge/common/reporting/ReportUserAdded.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/reporting/ReportUserAdded.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Reporting System
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: ReportUserAdded.class 3197 2004-07-28 17:34:45Z tperdue $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2003-03-16
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/reporting/Report.class');
+
+class ReportUserAdded extends Report {
+
+function ReportUserAdded($span,$start=0,$end=0) {
+	$this->Report();
+
+	if (!$start) {
+		$start=mktime(0,0,0,date('m'),1,date('Y'));;
+	}
+	if (!$end) {
+		$end=time();
+	} else {
+		$end--;
+	}
+
+	if (!$span || $span == REPORT_TYPE_MONTHLY) {
+
+		$res=db_query("SELECT * FROM rep_users_added_monthly 
+			WHERE month BETWEEN '$start' AND '$end' ORDER BY month");
+
+	} elseif ($span == REPORT_TYPE_WEEKLY) {
+
+		$res=db_query("SELECT * FROM rep_users_added_weekly 
+			WHERE week BETWEEN '$start' AND '$end' ORDER BY week");
+
+	} elseif ($span == REPORT_TYPE_DAILY) {
+
+		$res=db_query("SELECT * FROM rep_users_added_daily 
+			WHERE day BETWEEN '$start' AND '$end' ORDER BY day ASC");
+
+	}
+
+	$this->start_date=$start;
+	$this->end_date=$end;
+
+	if (!$res || db_error()) {
+		$this->setError('ReportUserAdded:: '.db_error());
+		return false;
+	}
+	$this->setSpan($span);
+	$this->setDates($res,0);
+	$this->setData($res,1);
+	return true;
+}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/reporting/ReportUserCum.class
===================================================================
--- trunk/gforge_base/gforge/common/reporting/ReportUserCum.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/reporting/ReportUserCum.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Reporting System
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: ReportUserCum.class 3197 2004-07-28 17:34:45Z tperdue $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2003-03-16
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/reporting/Report.class');
+
+class ReportUserCum extends Report {
+
+function ReportUserCum($span,$start=0,$end=0) {
+	$this->Report();
+
+	if (!$start) {
+		$start=mktime(0,0,0,date('m'),1,date('Y'));;
+	}
+	if (!$end) {
+		$end=time();
+	} else {
+		$end--;
+	}
+
+	if (!$span || $span == REPORT_TYPE_MONTHLY) {
+
+		$res=db_query("SELECT * FROM rep_users_cum_monthly 
+			WHERE month BETWEEN '$start' AND '$end' ORDER BY month");
+
+	} elseif ($span == REPORT_TYPE_WEEKLY) {
+
+		$res=db_query("SELECT * FROM rep_users_cum_weekly 
+			WHERE week BETWEEN '$start' AND '$end' ORDER BY week");
+
+	} elseif ($span == REPORT_TYPE_DAILY) {
+
+		$res=db_query("SELECT * FROM rep_users_cum_daily 
+			WHERE day BETWEEN '$start' AND '$end' ORDER BY day ASC");
+
+	}
+
+	$this->start_date=$start;
+	$this->end_date=$end;
+
+	if (!$res || db_error()) {
+		$this->setError('ReportUserAdded:: '.db_error());
+		return false;
+	}
+	$this->setSpan($span);
+	$this->setDates($res,0);
+	$this->setData($res,1);
+	return true;
+}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/reporting/ReportUserTime.class
===================================================================
--- trunk/gforge_base/gforge/common/reporting/ReportUserTime.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/reporting/ReportUserTime.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,106 @@
+<?php
+/**
+ * Reporting System
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: ReportUserTime.class 3197 2004-07-28 17:34:45Z tperdue $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2003-03-16
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+require_once('common/reporting/Report.class');
+
+class ReportUserTime extends Report {
+
+function ReportUserTime($user_id,$type,$start=0,$end=0) {
+	$this->Report();
+
+	if (!$start) {
+		$start=mktime(0,0,0,date('m'),1,date('Y'));;
+	}
+	if (!$end) {
+		$end=time();
+	} else {
+		$end--;
+	}
+
+	if (!$user_id) {
+		$this->setError('No User_id');
+		return false;
+	}
+
+	//
+	//	Task report
+	//
+	if (!$type || $type=='tasks') {
+
+		$res=db_query("SELECT pt.summary,sum(rtt.hours) AS hours 
+			FROM rep_time_tracking rtt, project_task pt
+			WHERE rtt.user_id='$user_id' 
+			AND rtt.report_date BETWEEN '$start' AND '$end' 
+			AND rtt.project_task_id=pt.project_task_id
+			GROUP BY pt.summary
+			ORDER BY hours DESC");
+
+	//
+	//	Category report
+	//
+	} elseif ($type=='category') {
+
+		$res=db_query("SELECT rtc.category_name, sum(rtt.hours) AS hours 
+			FROM rep_time_tracking rtt, rep_time_category rtc
+			WHERE rtt.user_id='$user_id' 
+			AND rtt.report_date BETWEEN '$start' AND '$end' 
+			AND rtt.time_code=rtc.time_code
+			GROUP BY rtc.category_name
+			ORDER BY hours DESC");
+
+	//
+	//	Percentage this user spent on a specific subproject
+	//
+	} elseif ($type=='subproject') {
+
+		$res=db_query("SELECT pgl.project_name, sum(rtt.hours) AS hours 
+			FROM rep_time_tracking rtt, project_task pt, project_group_list pgl
+			WHERE rtt.user_id='$user_id' 
+			AND rtt.report_date BETWEEN '$start' AND '$end' 
+			AND rtt.project_task_id=pt.project_task_id
+			AND pt.group_project_id=pgl.group_project_id
+			GROUP BY pgl.project_name
+			ORDER BY hours DESC");
+
+	}
+
+	$this->start_date=$start;
+	$this->end_date=$end;
+
+	if (!$res || db_error()) {
+		$this->setError('ReportUserAct:: '.db_error());
+		return false;
+	}
+
+	$this->labels = util_result_column_to_array($res,0);
+	$this->setData($res,1);
+	return true;
+}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/reporting/TimeEntry.class
===================================================================
--- trunk/gforge_base/gforge/common/reporting/TimeEntry.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/reporting/TimeEntry.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,161 @@
+<?php
+
+/**
+ * TimeEntry.class - Main time entry class
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+
+/**
+ * Base error class
+ */
+require_once 'common/include/Error.class';
+
+/**
+ * Time Entry model object
+ *
+ * This is a simple OO-wrapper to the existing time entry methods.  At some point someone will want
+ * to actually implement the procedural functions direct in this file.  This class is only used by
+ * the SOAP interface.
+ *
+ * @author Tony Bibbs <tony at geeklog.net>
+ * @copyright Copyright 2005, Tony Bibbs
+ * @version: $Id: TimeEntry.class 5407 2006-04-20 12:16:33Z danper $
+ * @todo This is just a wrapper to the existing procedural methods.  At some point someone will
+ * want to move that implementation here.
+ * @todo I'm sure this isn't code per the gForge coding standards.  This should be fixed.
+ *
+ */
+class TimeEntry extends Error {
+    /**
+     * Constructor
+     *
+     * @author Tony Bibbs <tony at geeklog.net>
+     * @access public
+     *
+     */
+    function TimeEntry() 
+    {
+    }
+
+    /**
+     * Creates a time entry record
+     *
+     * NOTE: this is a real hack as it uses the existing procedural code to call on functionality.
+     * The biggest drawback is that this method will not be able to return the Primary Key for the
+     * time entry record because the key is a unixtimestamp (see the way the UI uses timeadd.php
+     * to fully appreciate what I mean).
+     *
+     * @author Tony Bibbs <tony at geeklog.net>
+     * @access public
+     * @param int $projectTaskId The project task the user is reporting time to
+     * @param int $week The week the time being reported was done
+     * @param int $daysAdjust Represents the offset to add to the given week to specify the day
+     * @param int $timeCode The type of work that was done (general categorization)
+     * @param float $hours The actual time spent
+     * @return int This will be the Artificat ID otherwise it will be false if an error occurred
+     * @todo I'm quite concerned that none of the form data is being sanitized for things like
+     * unwanted HTML, JavaSript and SQL Injection.  Might be worth adding that sort of filtering
+     * as provided by the KSES Filter (search Google).
+     * @todo The check that looks to see if this method works is not language independent.  
+     * someone that better understands how that all works will want to remove the hardcoded
+     * 'successfully added'.
+     *
+     */
+    function create($projectTaskId, $week, $daysAdjust, $timeCode, $hours)
+    {
+	global $Language;
+
+        $report_date=($week + ($days_adjust*REPORT_DAY_SPAN))+(12*60*60);
+        $res=db_query("INSERT INTO rep_time_tracking (user_id,week,report_date,project_task_id,time_code,hours)
+                       VALUES ('".user_getid()."','$week','$report_date','$projectTaskId','$timeCode','$hours')");
+        //$res=db_query("INSERT INTO rep_time_tracking (user_id,week,report_date,project_task_id,time_code,hours)
+        //               VALUES (103,'$week','$report_date','$projectTaskId','$timeCode','$hours')");
+        //print_r($res); exit;
+        if (!$res) {
+            exit_error('Error',db_error());
+        } else {
+            $feedback.=$Language->getText('reporting_ta','successfully_added');
+        }
+	return db_affected_rows($res);
+    }  
+
+    /**
+     * Updates a timeEntry record.
+     *
+     * This isn't supported by the current timeadd.php code so I'm assuming that all 
+     * that is expected is that instead of changing something you'd simply delete it
+     * and readd it.  Messy, IMHO, but I am still including this method here to let
+     * know I purposely left this unimplemented.
+     *
+     * @author Tony Bibbs <tony at geeklog.net>
+     * @access public
+     * @return boolean Always false
+     *
+     */
+    function update()
+    {
+        // Not supported in timeadd.php
+        return false;
+    } 
+
+    /**
+     * Deletes an existing timeEntry record
+     *
+     * @author Tony Bibbs <tony at geeklog.net>
+     * @access public
+     * @param int $projectTaskId ID for the task which the time entry record belongs to.
+     * @param int $reportDate
+     * @param int $oldTimeCode ID of time code that was associated with time entry record.
+     * @return boolean True if delete works, otherwise false)
+     *
+     */
+    function delete($projectTaskId, $reportDate, $oldTimeCode)
+    {
+        global $_POST;
+
+        // Trick procedural code into thinking this was posted via the HTML form
+        $_POST['submit'] = 1;
+        $_POST['delete'] = 1;
+
+        // Sanitize the data at some point.
+        $project_task_id = $projectTaskId;
+        $report_date = $reportDate;
+        $old_time_code = $oldTimeCode;
+
+        // Prepare to have the procedural code process all of this.  We'll need to buffer any
+        // output so we can gracefully ignore it since this class is only used by the SOAP
+        // interface
+        ob_start();
+
+        // Now pull in the procedural code to handle the processing.
+        require_once 'www/reporting/timeadd.php';
+        $tmpOutput = ob_get_contents();
+
+        // Now discard any output.
+        ob_clean();        
+        
+        if (!stristr($tmpOutput, 'successfully deleted')) return false;
+        return true;
+    }
+
+    function getTimeCodes()
+    {
+    }
+}
+
+?>

Added: trunk/gforge_base/gforge/common/reporting/report_utils.php
===================================================================
--- trunk/gforge_base/gforge/common/reporting/report_utils.php	                        (rev 0)
+++ trunk/gforge_base/gforge/common/reporting/report_utils.php	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,207 @@
+<?php
+/**
+ * Reporting System
+ *
+ * Copyright 2004 (c) GForge LLC
+ *
+ * @version   $Id: report_utils.php 3403 2004-09-30 19:01:01Z tom $
+ * @author Tim Perdue tim at gforge.org
+ * @date 2003-03-16
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+function report_header($title) {
+	global $HTML,$sys_name,$Language;
+	echo $HTML->header(array('title'=>" ".$Language->getText('reporting','title').": " . $title));
+	if (isset($GLOBALS['feedback'])) {
+		echo html_feedback_top($GLOBALS['feedback']);
+	}
+	echo "<h2>".$Language->getText('reporting','subtitle',$sys_name)."</h2><p>";
+}
+
+function report_footer() {
+	global $HTML;
+	echo html_feedback_bottom($GLOBALS['feedback']);
+	echo $HTML->footer(array());
+}
+
+function report_span_box($name='SPAN', $selected='1', $suppress_daily=false) {
+	global $Language;
+	if ($suppress_daily) {
+		$vals=array(2,3);
+		$titles=array($Language->getText('reporting','weekly'),
+			$Language->getText('reporting','monthly'));
+	} else {
+		$vals=array(1,2,3);
+		$titles=array($Language->getText('reporting','daily'),
+			$Language->getText('reporting','weekly'),
+			$Language->getText('reporting','monthly'));
+	}
+	return html_build_select_box_from_arrays ($vals,$titles,$name,$selected,false);
+}
+
+function report_weeks_box($Report, $name='week', $selected=false) {
+	global $sys_shortdatefmt, $Language;
+	$arr =& $Report->getWeekStartArr();
+
+	$arr2=array();
+	for ($i=0; $i<count($arr); $i++) {
+		$arr2[$i]=date($sys_shortdatefmt, $arr[$i]) .' '.$Language->getText('general', 'to').' '. date($sys_shortdatefmt, ($arr[$i]+6*24*60*60));
+	}
+
+	return html_build_select_box_from_arrays ($arr,$arr2,$name,$selected,false);
+}
+
+function report_day_adjust_box($Report, $name='days_adjust', $selected=false) {
+	global $Language;
+	$days[]='0.0';
+	$days[]='1';
+	$days[]='2';
+	$days[]='3';
+	$days[]='4';
+	$days[]='5';
+	$days[]='6';
+	$names[]=$Language->getText('calendar','sunday');
+	$names[]=$Language->getText('calendar','monday');
+	$names[]=$Language->getText('calendar','tuesday');
+	$names[]=$Language->getText('calendar','wednesday');
+	$names[]=$Language->getText('calendar','thursday');
+	$names[]=$Language->getText('calendar','friday');
+	$names[]=$Language->getText('calendar','saturday');
+	return html_build_select_box_from_arrays ($days,$names,$name,$selected,false);
+
+//	return html_build_select_box_from_arrays (array_reverse(array_values($Report->adjust_days)),array_reverse(array_keys($Report->adjust_days)),$name,$selected,false);
+}
+
+function report_months_box($Report, $name='month', $selected=false) {
+	global $Language;
+	$arr =& $Report->getMonthStartArr();
+
+	$arr2=array();
+	for ($i=0; $i<count($arr); $i++) {
+		$arr2[$i]=date($Language->getText('calendar', 'monthdatefmt'),$arr[$i]);
+	}
+
+	return html_build_select_box_from_arrays ($arr,$arr2,$name,$selected,false);
+}
+
+function report_useract_box($name='dev_id', $selected='1', $start_with='') {
+
+	if ($start_with) {
+		$sql2=" AND lastname ILIKE '$start_with%' ";
+	}
+
+	$res=db_query("SELECT user_id,realname 
+		FROM users 
+		WHERE status='A' $sql2 
+		AND (exists (SELECT user_id FROM rep_user_act_daily WHERE user_id=users.user_id)) ORDER BY lastname");
+	return html_build_select_box($res, $name, $selected, false);
+}
+
+function report_usertime_box($name='dev_id', $selected='1', $start_with='') {
+
+	if ($start_with) {
+		$sql2=" AND lastname ILIKE '$start_with%' ";
+	}
+
+	$res=db_query("SELECT user_id,realname 
+		FROM users 
+		WHERE status='A' $sql2 
+		AND (exists (SELECT user_id FROM rep_time_tracking WHERE user_id=users.user_id)) ORDER BY lastname");
+	return html_build_select_box($res, $name, $selected, false);
+}
+
+function report_group_box($name='g_id', $selected='1') {
+
+	$res=db_query("SELECT group_id,group_name FROM groups WHERE status='A' ORDER BY group_name");
+	return html_build_select_box($res, $name, $selected, false);
+}
+
+function report_area_box($name='area', $selected='1') {
+	global $Language;
+	$arr[]='tracker';
+	$arr[]='forum';
+	$arr[]='docman';
+	$arr[]='taskman';
+	$arr[]='downloads';
+
+	$arr2[]=$Language->getText('group','short_tracker');
+	$arr2[]=$Language->getText('group','short_forum');
+	$arr2[]=$Language->getText('group','short_docman');
+	$arr2[]=$Language->getText('group','short_pm');
+	$arr2[]=$Language->getText('stats_site_utils','downloads');
+	return html_build_select_box_from_arrays ($arr,$arr2,$name,$selected,false);
+}
+
+function report_tracker_box($name='datatype', $selected='1') {
+	global $Language;
+	$arr[]=$Language->getText('group','short_bugs');
+	$arr[]=$Language->getText('group','short_support');
+	$arr[]=$Language->getText('group','short_patch');
+	$arr[]=$Language->getText('reporting','feature_req');
+	$arr[]=$Language->getText('reporting','other_trackers');
+	$arr[]=$Language->getText('reporting','forum_messages');
+	$arr[]=$Language->getText('group','short_pm');
+	$arr[]=$Language->getText('stats_site_utils','downloads');
+
+	$arr2[]='1';
+	$arr2[]='2';
+	$arr2[]='3';
+	$arr2[]='4';
+	$arr2[]='0';
+	$arr2[]='5';
+	$arr2[]='6';
+	$arr2[]='7';
+	return html_build_select_box_from_arrays ($arr2,$arr,$name,$selected,false);
+}
+
+function report_time_category_box($name='category',$selected=false) {
+	global $report_time_category_res;
+	if (!$report_time_category_res) {
+		$report_time_category_res=db_query("SELECT * FROM rep_time_category");
+	}
+	return html_build_select_box($report_time_category_res,$name,$selected,false);
+}
+
+//
+//	Takes an array of labels and an array values and removes vals < 2% and sets up an "other"
+//
+function report_pie_arr($labels, $vals) {
+	global $pie_labels,$pie_vals;
+	//first get sum of all values
+	for ($i=0; $i<count($vals); $i++) {
+		$total += $vals[$i];
+	}
+
+	//now prune out vals where < 2%
+	for ($i=0; $i<count($vals); $i++) {
+		if (($vals[$i]/$total) < .02) {
+			$rem += $vals[$i];
+		} else {
+			$pie_labels[]=util_unconvert_htmlspecialchars($labels[$i])." (". number_format($vals[$i],1) .") ";
+			$pie_vals[]=number_format($vals[$i],1);
+		}
+	}
+	if ($rem > 0) {
+		$pie_labels[]='Other'." (". number_format($rem,1) .") ";
+		$pie_vals[]=number_format($rem,1);
+	}
+	
+}
+
+?>

Added: trunk/gforge_base/gforge/common/scm/SCMFactory.class
===================================================================
--- trunk/gforge_base/gforge/common/scm/SCMFactory.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/scm/SCMFactory.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,72 @@
+<?php
+/**
+ * GForge SCM Facility
+ *
+ * Copyright 2004 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: SCMFactory.class 3226 2004-08-02 18:55:05Z kikov $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+
+require_once('common/include/Error.class');
+require_once('common/include/PluginManager.class');
+
+class SCMFactory extends Error {
+
+	/**
+	 * The scms array.
+	 *
+	 * @var  array  scms.
+	 */
+	var $scms;
+	var $fetched_rows;
+
+	/**
+	 *  Constructor.
+	 *
+	 *	@return	boolean	success.
+	 */
+	function SCMFactory() {
+		$this->Error();
+		if (!$sys_use_scm) {
+			$this->setError('SCMFactory::sys_use_scm');
+			return false;
+		}
+		return true;
+	}
+
+	/**
+	 *	getSCMs - get an array of Plugin SCM objects.
+	 *
+	 *	@return	array	The array of SCM objects.
+	 */
+	function &getSCMs() {
+		$scm_plugins = array();
+		if ($this->scms) {
+			return $this->scms;
+		}
+		$hookParams['scm_plugins']=& $scm_plugins;
+		plugin_hook("scm_plugin", $hookParams);
+		$this->scms= $scm_plugins;
+		return $this->scms;
+	}
+
+}
+
+?>

Added: trunk/gforge_base/gforge/common/search/ArtifactSearchQuery.class
===================================================================
--- trunk/gforge_base/gforge/common/search/ArtifactSearchQuery.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/search/ArtifactSearchQuery.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * GForge Search Engine
+ *
+ * Portions Copyright 1999-2001 (c) VA Linux Systems
+ * The rest Copyright 2004 (c) Guillaume Smet / Open Wide
+ *
+ * http://gforge.org
+ *
+ * @version $Id: ArtifactSearchQuery.class 5270 2006-02-05 17:12:19Z tperdue $
+ */
+
+require_once('common/search/SearchQuery.class');
+
+class ArtifactSearchQuery extends SearchQuery {
+	
+	/**
+	 * group id
+	 *
+	 * @var int $groupId
+	 */
+	var $groupId;
+	
+	/**
+	 * artifact id
+	 *
+	 * @var int $artifactId
+	 */
+	var $artifactId;
+	
+	/**
+	 * Constructor
+	 *
+	 * @param string $words words we are searching for
+	 * @param int $offset offset
+	 * @param boolean $isExact if we want to search for all the words or if only one matching the query is sufficient
+	 * @param int $groupId group id
+	 * @param int $artifactId artifact id
+	 */
+	function ArtifactSearchQuery($words, $offset, $isExact, $groupId, $artifactId) {
+		$this->groupId = $groupId;
+		$this->artifactId = $artifactId;
+		
+		$this->SearchQuery($words, $offset, $isExact);
+	}
+
+	/**
+	 * getQuery - get the sql query built to get the search results
+	 *
+	 * @return string sql query to execute
+	 */
+	function getQuery() {
+		global $sys_use_fti;
+		if ($sys_use_fti) {
+			$words=$this->getFormattedWords();
+			$artifactId = $this->artifactId;
+			$sql = "SELECT DISTINCT ON (artifact.artifact_id) artifact.artifact_id,
+				artifact.group_artifact_id, artifact.summary AS summary,
+				artifact.open_date, users.realname, artifact_group_list.name
+				FROM artifact LEFT JOIN artifact_message USING (artifact_id), users,
+				artifact_group_list
+				WHERE users.user_id = artifact.submitted_by
+				AND artifact_group_list.group_artifact_id = artifact.group_artifact_id
+				AND artifact_group_list.group_artifact_id='$artifactId'
+				AND artifact.artifact_id IN (
+				SELECT artifact_id FROM artifact_idx,
+				to_tsquery('$words') AS q WHERE vectors @@ q ORDER BY rank(vectors, q) DESC)
+				OR artifact.artifact_id IN (
+				SELECT artifact_id FROM artifact_message_idx,
+				to_tsquery('$words') AS q WHERE vectors @@ q ORDER BY rank(vectors, q) DESC)";
+		} else {
+			$sql = 'SELECT DISTINCT ON (a.group_artifact_id,a.artifact_id) a.group_artifact_id,a.artifact_id,a.summary,a.open_date,users.realname '
+				. 'FROM artifact a LEFT OUTER JOIN artifact_message am USING (artifact_id), users ' 
+				. 'WHERE a.group_artifact_id=\''.$this->artifactId.'\' '
+				. 'AND users.user_id=a.submitted_by '
+				. 'AND (('.$this->getIlikeCondition('a.details', $this->words).') ' 
+				. 'OR ('.$this->getIlikeCondition('a.summary', $this->words).') '
+				. 'OR ('.$this->getIlikeCondition('am.body', $this->words).')) '
+				. 'ORDER BY group_artifact_id ASC, a.artifact_id ASC';
+		}
+		return $sql;
+	}
+
+	/**
+	 * getSearchByIdQuery - get the sql query built to get the search results when we are looking for an int
+	 *
+	 * @return string sql query to execute
+	 */	
+	function getSearchByIdQuery() {
+		$sql = 'SELECT DISTINCT ON (a.group_artifact_id,a.artifact_id) a.group_artifact_id, a.artifact_id '
+			. 'FROM artifact a ' 
+			. 'WHERE a.group_artifact_id=\''.$this->artifactId.'\' '
+			. 'AND a.artifact_id=\''.$this->searchId.'\'';
+
+		return $sql;
+	}
+	
+}
+
+?>

Added: trunk/gforge_base/gforge/common/search/DocsSearchQuery.class
===================================================================
--- trunk/gforge_base/gforge/common/search/DocsSearchQuery.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/search/DocsSearchQuery.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,128 @@
+<?php
+/**
+ * GForge Search Engine
+ *
+ * Copyright 2004 (c) Dominik Haas, GForge Team
+ *
+ * http://gforge.org
+ *
+ * @version $Id: DocsSearchQuery.class 5273 2006-02-08 18:47:59Z danper $
+ */
+
+require_once('common/search/SearchQuery.class');
+
+class DocsSearchQuery extends SearchQuery {
+	
+	/**
+	* group id
+	*
+	* @var int $groupId
+	*/
+	var $groupId;
+	
+	/**
+	* flag if non public items are returned
+	*
+	* @var boolean $showNonPublic
+	*/	
+	var $showNonPublic;
+	
+	/**
+	 * Constructor
+	 *
+	 * @param string $words words we are searching for
+	 * @param int $offset offset
+	 * @param boolean $isExact if we want to search for all the words or if only one matching the query is sufficient
+	 * @param int $groupId group id
+	 * @param array $sections sections to search in
+	 * @param boolean $showNonPublic flag if private sections are searched too
+	 */
+	function DocsSearchQuery($words, $offset, $isExact, $groupId, $sections=SEARCH__ALL_SECTIONS, $showNonPublic=false) {	
+		$this->groupId = $groupId;
+		$this->showNonPublic = $showNonPublic;
+		
+		$this->SearchQuery($words, $offset, $isExact);
+		
+		$this->setSections($sections);
+	}
+
+	/**
+	 * getQuery - get the sql query built to get the search results
+	 *
+	 * @return string sql query to execute
+	 */
+	function getQuery() {
+		global $sys_use_fti;
+		if ($sys_use_fti) {
+			return $this->getFTIQuery();
+		} else {
+			$sql = 'SELECT doc_data.docid, doc_data.title, doc_data.description, doc_groups.groupname'
+				.' FROM doc_data, doc_groups'
+				.' WHERE doc_data.doc_group = doc_groups.doc_group'
+				.' AND doc_data.group_id ='.$this->groupId;
+			if ($this->sections != SEARCH__ALL_SECTIONS) {
+				$sql .= ' AND doc_groups.doc_group IN ('.$this->sections.') ';
+			}
+			if ($this->showNonPublic) {
+				$sql .= ' AND doc_data.stateid IN (1, 4, 5)';
+			} else {
+				$sql .= ' AND doc_data.stateid = 1';
+			}
+			$sql .= ' AND (('.$this->getIlikeCondition('title', $this->words).')' 
+				.' OR ('.$this->getIlikeCondition('description', $this->words).'))'
+				.' ORDER BY doc_groups.groupname, doc_data.docid';
+		}
+		return $sql;
+	}
+	
+	function getFTIQuery() {
+		if ($this->showNonPublic) {
+			$nonPublic = "1, 4, 5";
+		} else {
+			$nonPublic = "1";
+		}
+		if ($this->sections != SEARCH__ALL_SECTIONS) {
+			$sections = "AND doc_groups.doc_group IN ($this->sections)";
+		} else {
+			$sections = '';
+		}
+		$words = $this->getFormattedWords();
+		$group_id=$this->groupId;
+		$sql="SELECT doc_data.docid, headline(doc_data.title, q) AS title,
+			headline(doc_data.description, q) AS description, doc_groups.groupname
+			FROM doc_data, doc_groups, to_tsquery('$words') AS q, doc_data_idx
+			WHERE doc_data.doc_group = doc_groups.doc_group
+			AND  doc_data.docid = doc_data_idx.docid AND vectors @@ q
+			AND doc_data.group_id = '$group_id'
+			$sections
+			AND doc_data.stateid IN ($nonPublic)
+			ORDER BY rank(vectors, q) DESC";
+		return $sql;
+	}
+
+	/**
+	 * getSections - returns the list of available doc groups
+	 *
+	 * @param $groupId int group id
+	 * @param $showNonPublic boolean if we should consider non public sections
+	 */
+	function getSections($groupId, $showNonPublic=false) {
+		$sql = 'SELECT doc_groups.doc_group, doc_groups.groupname FROM doc_groups, doc_data'
+			.' WHERE doc_groups.doc_group = doc_data.doc_group AND doc_groups.group_id = '.$groupId;
+		if ($showNonPublic) {
+			$sql .= ' AND doc_data.stateid IN (1, 4, 5)';
+		} else {
+			$sql .= ' AND doc_data.stateid = 1';
+		}
+		$sql .= ' ORDER BY groupname';
+		
+		$sections = array();
+		$res = db_query($sql);
+		while($data = db_fetch_array($res)) {
+			$sections[$data['doc_group']] = $data['groupname'];
+		}
+		return $sections;
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/search/ExportProjectSearchQuery.class
===================================================================
--- trunk/gforge_base/gforge/common/search/ExportProjectSearchQuery.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/search/ExportProjectSearchQuery.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * GForge Search Engine
+ *
+ * Portions Copyright 1999-2001 (c) VA Linux Systems
+ * The rest Copyright 2004 (c) Guillaume Smet / Open Wide
+ *
+ * http://gforge.org
+ *
+ * @version $Id: ExportProjectSearchQuery.class 5273 2006-02-08 18:47:59Z danper $
+ */
+
+require_once('common/search/SearchQuery.class');
+
+class ExportProjectSearchQuery extends SearchQuery {
+
+	/**
+	 * Constructor
+	 *
+	 * @param string $words words we are searching for
+	 * @param int $offset offset
+	 * @param boolean $isExact if we want to search for all the words or if only one matching the query is sufficient
+	 */
+	function ExportProjectSearchQuery($words, $offset, $isExact) {	
+		$this->SearchQuery($words, $offset, $isExact, 200);
+	}
+
+	/**
+	 * getQuery - get the sql query built to get the search results
+	 *
+	 * @return string sql query to execute
+	 */
+	function getQuery() {
+		global $sys_use_fti;
+		if ($sys_use_fti) {
+			$words = $this->getFormattedWords();
+			$sql = "SELECT headline(group_name, q) as group_name,
+					headline(unix_group_name, q) as unix_group_name,
+					type_id,groups.group_id,headline(short_description, q) as short_description,
+					license,register_time FROM groups, to_tsquery('$words') AS q, groups_idx 
+					WHERE groups.group_id = groups_idx.group_id AND vectors @@ q AND 
+					status IN ('A', 'H') AND is_public='1' AND short_description <> '' 
+					ORDER BY rank(vectors, q) DESC";
+		} else {
+			$groupNameCond = $this->getIlikeCondition('group_name', $this->words);
+			$groupDescriptionCond = $this->getIlikeCondition('short_description', $this->words);
+			$groupUnixNameCond = $this->getIlikeCondition('unix_group_name', $this->words);
+			
+			$sql = 'SELECT group_name,unix_group_name,type_id,groups.group_id, '
+				.'short_description,license,register_time '
+				.'FROM groups '
+				.'WHERE status IN (\'A\', \'H\') '
+				.'AND is_public=\'1\' '
+				.'AND groups.short_description<>\'\' '
+				.'AND (('.$groupNameCond.') OR ('.$groupDescriptionCond.') OR ('.$groupUnixNameCond.'))';
+		}
+		return $sql;
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/search/ForumSearchQuery.class
===================================================================
--- trunk/gforge_base/gforge/common/search/ForumSearchQuery.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/search/ForumSearchQuery.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * GForge Search Engine
+ *
+ * Portions Copyright 1999-2001 (c) VA Linux Systems
+ * The rest Copyright 2004 (c) Guillaume Smet / Open Wide
+ *
+ * http://gforge.org
+ *
+ * @version $Id: ForumSearchQuery.class 5273 2006-02-08 18:47:59Z danper $
+ */
+
+require_once('common/search/SearchQuery.class');
+
+class ForumSearchQuery extends SearchQuery {
+	
+	/**
+	 * group id
+	 *
+	 * @var int $groupId
+	 */
+	var $groupId;
+	
+	/**
+	 * forum id
+	 *
+	 * @var int $groupId
+	 */
+	var $forumId;
+	
+	/**
+	 * Constructor
+	 *
+	 * @param string $words words we are searching for
+	 * @param int $offset offset
+	 * @param boolean $isExact if we want to search for all the words or if only one matching the query is sufficient
+	 * @param int $groupId group id
+	 * @param int $forumId forum id
+	 */
+	function ForumSearchQuery($words, $offset, $isExact, $groupId, $forumId) {
+		$this->groupId = $groupId;
+		$this->forumId = $forumId;
+		
+		$this->SearchQuery($words, $offset, $isExact);
+	}
+
+	/**
+	 * getQuery - get the sql query built to get the search results
+	 *
+	 * @return string sql query to execute
+	 */
+	function getQuery() {
+		global $sys_use_fti;
+		if ($sys_use_fti) {
+			$words = $this->getFormattedWords();
+			$sql = "SELECT forum.msg_id, headline(forum.subject, q) AS subject, forum.post_date, users.realname "
+					." FROM forum, users, to_tsquery('$words') AS q, forum_idx as fi "
+					." WHERE fi.msg_id = forum.msg_id AND vectors @@ q "
+					." AND users.user_id=forum.posted_by AND forum.group_forum_id=$this->forumId "
+					." ORDER BY rank(vectors, q) DESC";
+		} else {
+			$sql = 'SELECT forum.msg_id, forum.subject, forum.post_date, users.realname '
+				. 'FROM forum,users '
+				. 'WHERE users.user_id=forum.posted_by '
+				. 'AND (('.$this->getIlikeCondition('forum.body', $this->words).') '
+				. 'OR ('.$this->getIlikeCondition('forum.subject', $this->words).')) '
+				. 'AND forum.group_forum_id=\''.$this->forumId.'\' '
+				. 'GROUP BY msg_id, subject, post_date, realname';
+		}
+		return $sql;
+	}
+	
+	/**
+	 * getSearchByIdQuery - get the sql query built to get the search results when we are looking for an int
+	 *
+	 * @return string sql query to execute
+	 */	
+	function getSearchByIdQuery() {
+		$sql = 'SELECT msg_id '
+			. 'FROM forum '
+			. 'WHERE msg_id=\''.$this->searchId.'\' '
+			. 'AND group_forum_id=\''.$this->forumId.'\'';
+
+		return $sql;
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/search/ForumsSearchQuery.class
===================================================================
--- trunk/gforge_base/gforge/common/search/ForumsSearchQuery.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/search/ForumsSearchQuery.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,132 @@
+<?php
+/**
+ * GForge Search Engine
+ *
+ * Copyright 2004 (c) Dominik Haas, GForge Team
+ *
+ * http://gforge.org
+ *
+ * @version $Id: ForumsSearchQuery.class 5273 2006-02-08 18:47:59Z danper $
+ */
+ 
+require_once('common/search/SearchQuery.class');
+
+class ForumsSearchQuery extends SearchQuery {
+	
+	/**
+	* group id
+	*
+	* @var int $groupId
+	*/
+	var $groupId;
+	
+	/**
+	* flag if non public items are returned
+	*
+	* @var boolean $showNonPublic
+	*/	
+	var $showNonPublic;
+	
+	/**
+	 * Constructor
+	 *
+	 * @param string $words words we are searching for
+	 * @param int $offset offset
+	 * @param boolean $isExact if we want to search for all the words or if only one matching the query is sufficient
+	 * @param int $groupId group id
+	 * @param array $sections sections to search in
+	 * @param boolean $showNonPublic flag if private sections are searched too
+	 */
+	function ForumsSearchQuery($words, $offset, $isExact, $groupId, $sections=SEARCH__ALL_SECTIONS, $showNonPublic=false) {
+		$this->groupId = $groupId;
+		$this->showNonPublic = $showNonPublic;
+		
+		$this->SearchQuery($words, $offset, $isExact);
+		
+		$this->setSections($sections);
+	}
+
+	/**
+	 * getQuery - get the sql query built to get the search results
+	 *
+	 * @return string sql query to execute
+	 */
+	function getQuery() {
+		global $sys_use_fti;
+		if ($sys_use_fti) {
+			$sql = 'SELECT forum.msg_id, headline(forum.subject, q) AS subject, forum.post_date, users.realname, forum_group_list.forum_name '
+				. 'FROM forum, users, forum_group_list, forum_idx, to_tsquery(\''.
+				  $this->getFormattedWords().'\') as q '
+				. 'WHERE users.user_id = forum.posted_by '
+				. 'AND vectors @@ q AND forum.msg_id = forum_idx.msg_id '
+				. 'AND forum_group_list.group_forum_id = forum.group_forum_id '
+				. 'AND forum_group_list.is_public <> 9 '			
+				. 'AND forum.group_forum_id IN (SELECT group_forum_id FROM forum_group_list WHERE group_id = '.$this->groupId.') ';
+			if ($this->sections != SEARCH__ALL_SECTIONS) {
+				$sql .= 'AND forum_group_list.group_forum_id IN ('.$this->sections.') ';
+			}
+			if (!$this->showNonPublic) {
+				$sql .= 'AND forum_group_list.is_public = 1 ';
+			}
+			$sql .= 'ORDER BY forum_group_list.forum_name ASC, forum.msg_id ASC, rank(vectors, q) DESC';
+		} else {
+			$sql = 'SELECT forum.msg_id, forum.subject, forum.post_date, users.realname, forum_group_list.forum_name '
+				. 'FROM forum, users, forum_group_list '
+				. 'WHERE users.user_id = forum.posted_by '
+				. 'AND forum_group_list.group_forum_id = forum.group_forum_id '
+				. 'AND forum_group_list.is_public <> 9 '			
+				. 'AND forum.group_forum_id IN (SELECT group_forum_id FROM forum_group_list WHERE group_id = '.$this->groupId.') ';
+			if ($this->sections != SEARCH__ALL_SECTIONS) {
+				$sql .= 'AND forum_group_list.group_forum_id IN ('.$this->sections.') ';
+			}
+			if (!$this->showNonPublic) {
+				$sql .= 'AND forum_group_list.is_public = 1 ';
+			}
+			$sql .= 'AND (('.$this->getIlikeCondition('forum.body', $this->words).') '
+				. 'OR ('.$this->getIlikeCondition('forum.subject', $this->words).')) '
+				. 'ORDER BY forum_group_list.forum_name, forum.msg_id';
+		}
+		return $sql;
+	}
+
+	/**
+	 * getSearchByIdQuery - get the sql query built to get the search results when we are looking for an int
+	 *
+	 * @return string sql query to execute
+	 */	
+	function getSearchByIdQuery() {
+		$sql = 'SELECT msg_id '
+			. 'FROM forum, forum_group_list '
+			. 'WHERE msg_id=\''.$this->searchId.'\' '
+			. 'AND forum_group_list.group_forum_id = forum.group_forum_id '
+			. 'AND group_forum_id=\''.$this->forumId.'\'';
+		if (!$this->showNonPublic) {
+			$sql .= ' AND forum_group_list.is_public = 1';
+		}
+
+		return $sql;
+	}
+	
+	/**
+	 * getSections - returns the list of available forums
+	 *
+	 * @param $groupId int group id
+	 * @param $showNonPublic boolean if we should consider non public sections
+	 */
+	function getSections($groupId, $showNonPublic=false) {
+		$sql = 'SELECT group_forum_id, forum_name FROM forum_group_list WHERE group_id = '.$groupId.' AND is_public <> 9';
+		if (!$showNonPublic) {
+			$sql .= ' AND is_public = 1';
+		}
+		$sql .= ' ORDER BY forum_name';
+		
+		$sections = array();
+		$res = db_query($sql);
+		while($data = db_fetch_array($res)) {
+			$sections[$data['group_forum_id']] = $data['forum_name'];
+		}
+		return $sections;
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/search/FrsSearchQuery.class
===================================================================
--- trunk/gforge_base/gforge/common/search/FrsSearchQuery.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/search/FrsSearchQuery.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,112 @@
+<?php
+/**
+ * GForge Search Engine
+ *
+ * Copyright 2004 (c) Dominik Haas, GForge Team
+ *
+ * http://gforge.org
+ *
+ * @version $Id: FrsSearchQuery.class 5270 2006-02-05 17:12:19Z tperdue $
+ */
+
+require_once('common/search/SearchQuery.class');
+
+class FrsSearchQuery extends SearchQuery {
+	
+	/**
+	* group id
+	*
+	* @var int $groupId
+	*/
+	var $groupId;
+	
+	/**
+	* flag if non public items are returned
+	*
+	* @var boolean $showNonPublic
+	*/	
+	var $showNonPublic;
+	
+	/**
+	 * Constructor
+	 *
+	 * @param string $words words we are searching for
+	 * @param int $offset offset
+	 * @param boolean $isExact if we want to search for all the words or if only one matching the query is sufficient
+	 * @param int $groupId group id
+	 * @param array $sections sections to search in
+	 */
+	function FrsSearchQuery($words, $offset, $isExact, $groupId, $sections=SEARCH__ALL_SECTIONS, $showNonPublic=false) {	
+		$this->groupId = $groupId;
+		$this->showNonPublic = $showNonPublic;
+		
+		$this->SearchQuery($words, $offset, $isExact);
+		
+		$this->setSections($sections);
+	}
+	
+	/**
+	 * getQuery - get the sql query built to get the search results
+	 *
+	 * @return string sql query to execute
+	 */
+	function getQuery() {
+		global $sys_use_fti;
+		if ($sys_use_fti) {
+			$nonPublic = 'false';
+			$sections = '';
+			if ($this->showNonPublic) {
+				$nonPublic = 'true';
+			}
+			if ($this->sections != SEARCH__ALL_SECTIONS) {
+				$sections = $this->sections;
+			}
+			$sql = "SELECT * FROM frs_search('".$this->getFormattedWords()."', ".$this->groupId.", '$sections', $nonPublic)";
+		} else {
+			$sql = 'SELECT frs_package.name as package_name, frs_release.name as release_name, frs_release.release_date, frs_release.release_id, users.realname'
+				. ' FROM frs_file, frs_release, users, frs_package'
+				. ' WHERE frs_release.released_by = users.user_id'
+				. ' AND frs_package.package_id = frs_release.package_id'
+				. ' AND frs_file.release_id=frs_release.release_id'
+				. ' AND frs_package.group_id='.$this->groupId;
+			
+			if ($this->sections != SEARCH__ALL_SECTIONS) {
+				$sql .= ' AND frs_package.package_id IN ('.$this->sections.') ';
+			}
+			if(!$this->showNonPublic) {
+				$sql .= ' AND is_public=1';
+			}
+	
+			$sql .= ' AND (('.$this->getIlikeCondition('frs_release.changes', $this->words).')' 
+				. ' OR ('.$this->getIlikeCondition('frs_release.notes', $this->words).')'
+				. ' OR ('.$this->getIlikeCondition('frs_release.name', $this->words).')'
+				. ' OR ('.$this->getIlikeCondition('frs_file.filename', $this->words).'))'
+				. ' ORDER BY frs_package.name, frs_release.name';
+		}
+		return $sql;
+	}
+	
+	/**
+	 * getSections - returns the list of available forums
+	 *
+	 * @param $groupId int group id
+	 * @param $showNonPublic boolean if we should consider non public sections
+	 */
+	function getSections($groupId, $showNonPublic) {
+		$sql = 'SELECT package_id, name FROM frs_package WHERE group_id = \''.$groupId.'\' ORDER BY name';
+		
+		if(!$showNonPublic) {
+			$sql .= ' AND is_public=1';
+		}
+		$sql .= ' ORDER BY name';
+		
+		$sections = array();
+		$res = db_query($sql);
+		while($data = db_fetch_array($res)) {
+			$sections[$data['package_id']] = $data['name'];
+		}
+		return $sections;
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/search/NewsSearchQuery.class
===================================================================
--- trunk/gforge_base/gforge/common/search/NewsSearchQuery.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/search/NewsSearchQuery.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,69 @@
+<?php
+/**
+ * GForge Search Engine
+ *
+ * Copyright 2004 (c) Dominik Haas, GForge Team
+ *
+ * http://gforge.org
+ *
+ * @version $Id: NewsSearchQuery.class 5273 2006-02-08 18:47:59Z danper $
+ */
+
+require_once('common/search/SearchQuery.class');
+
+class NewsSearchQuery extends SearchQuery {
+	
+	/**
+	* group id
+	*
+	* @var int $groupId
+	*/
+	var $groupId;
+	
+	/**
+	 * Constructor
+	 *
+	 * @param string $words words we are searching for
+	 * @param int $offset offset
+	 * @param boolean $isExact if we want to search for all the words or if only one matching the query is sufficient
+	 * @param int $groupId group id
+	 */
+	function NewsSearchQuery($words, $offset, $isExact, $groupId) {	
+		$this->groupId = $groupId;
+		
+		$this->SearchQuery($words, $offset, $isExact);
+	}
+
+	/**
+	 * getQuery - get the sql query built to get the search results
+	 *
+	 * @return string sql query to execute
+	 */
+	function getQuery() {
+		global $sys_use_fti;
+		if ($sys_use_fti) {
+			$words = $this->getFormattedWords();
+			$group_id=$this->groupId;
+			$sql = "SELECT headline(news_bytes.summary, q) as summary,
+				news_bytes.post_date,
+				news_bytes.forum_id,
+				users.realname
+				FROM news_bytes, users, to_tsquery('$words') AS q, news_bytes_idx
+				WHERE (news_bytes.group_id='$group_id' AND news_bytes.is_approved <> '4'
+				AND news_bytes_idx.id = news_bytes.id
+				AND news_bytes.submitted_by=users.user_id) AND
+				(vectors @@ q)
+				ORDER BY rank(vectors, q) DESC";	
+		} else {
+			$sql = 'SELECT news_bytes.summary, news_bytes.post_date, news_bytes.forum_id, users.realname'
+				. ' FROM news_bytes, users'
+				. ' WHERE (group_id='.$this->groupId.' AND is_approved <> \'4\' AND news_bytes.submitted_by = users.user_id' 
+				. ' AND (('.$this->getIlikeCondition('summary', $this->words).')' 
+				. ' OR ('.$this->getIlikeCondition('details', $this->words).')))'
+				. ' ORDER BY post_date DESC';
+		}
+		return $sql;
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/search/PeopleSearchQuery.class
===================================================================
--- trunk/gforge_base/gforge/common/search/PeopleSearchQuery.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/search/PeopleSearchQuery.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * GForge Search Engine
+ *
+ * Portions Copyright 1999-2001 (c) VA Linux Systems
+ * The rest Copyright 2004 (c) Guillaume Smet / Open Wide
+ *
+ * http://gforge.org
+ *
+ * @version $Id: PeopleSearchQuery.class 5270 2006-02-05 17:12:19Z tperdue $
+ */
+
+require_once('common/search/SearchQuery.class');
+
+class PeopleSearchQuery extends SearchQuery {
+
+	/**
+	 * Constructor
+	 *
+	 * @param string $words words we are searching for
+	 * @param int $offset offset
+	 * @param boolean $isExact if we want to search for all the words or if only one matching the query is sufficient
+	 */
+	function PeopleSearchQuery($words, $offset, $isExact) {	
+		$this->SearchQuery($words, $offset, $isExact);
+	}
+
+	/**
+	 * getQuery - get the sql query built to get the search results
+	 *
+	 * @return string sql query to execute
+	 */
+	function getQuery() {
+		global $sys_use_fti;
+		if ($sys_use_fti) {
+			$sql = "SELECT * FROM users_search('".$this->getFormattedWords()."')";
+		} else {
+			$sql = 'SELECT user_name,user_id,realname ' 
+				. 'FROM users ' 
+				. 'WHERE (('.$this->getIlikeCondition('user_name', $this->words).') ' 
+				. 'OR ('.$this->getIlikeCondition('realname', $this->words).')) ' 
+				. 'AND (status=\'A\') ' 
+				. 'ORDER BY user_name';
+		}
+		return $sql;
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/search/ProjectSearchQuery.class
===================================================================
--- trunk/gforge_base/gforge/common/search/ProjectSearchQuery.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/search/ProjectSearchQuery.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * GForge Search Engine
+ *
+ * Portions Copyright 1999-2001 (c) VA Linux Systems
+ * The rest Copyright 2004 (c) Guillaume Smet / Open Wide
+ *
+ * http://gforge.org
+ *
+ * @version $Id: ProjectSearchQuery.class 5270 2006-02-05 17:12:19Z tperdue $
+ */
+
+require_once('common/search/SearchQuery.class');
+
+class ProjectSearchQuery extends SearchQuery {
+
+	/**
+	 * Constructor
+	 *
+	 * @param string $words words we are searching for
+	 * @param int $offset offset
+	 * @param boolean $isExact if we want to search for all the words or if only one matching the query is sufficient
+	 */
+	function ProjectSearchQuery($words, $offset, $isExact) {	
+		$this->SearchQuery($words, $offset, $isExact);
+	}
+
+	/**
+	 * getQuery - get the sql query built to get the search results
+	 *
+	 * @return string sql query to execute
+	 */
+	function getQuery() {
+		global $sys_use_fti;
+		if ($sys_use_fti) {
+			$sql = "SELECT * FROM groups_search('".$this->getFormattedWords()."')";
+		} else {
+			$groupNameCond = $this->getIlikeCondition('group_name', $this->words);
+			$groupDescriptionCond = $this->getIlikeCondition('short_description', $this->words);
+			$groupUnixNameCond = $this->getIlikeCondition('unix_group_name', $this->words);
+			
+			$sql = 'SELECT group_name, unix_group_name, type_id, group_id, short_description '
+				.'FROM groups '
+				.'WHERE status IN (\'A\', \'H\') '
+				.'AND is_public=\'1\' '
+				.'AND (('.$groupNameCond.') OR ('.$groupDescriptionCond.') OR ('.$groupUnixNameCond.'))';
+		}
+		return $sql;
+	}
+	
+}
+
+?>

Added: trunk/gforge_base/gforge/common/search/SearchQuery.class
===================================================================
--- trunk/gforge_base/gforge/common/search/SearchQuery.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/search/SearchQuery.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,264 @@
+<?php
+
+/**
+ * GForge Search Engine
+ *
+ * Portions Copyright 1999-2001 (c) VA Linux Systems
+ * The rest Copyright 2004 (c) Guillaume Smet / Open Wide
+ *
+ * http://gforge.org
+ *
+ * @version $Id: SearchQuery.class 5270 2006-02-05 17:12:19Z tperdue $
+ */
+
+class SearchQuery extends Error {
+	/**
+	 * the operator between each part of the query. Can be AND or OR.
+	 *
+	 * @var string $operator
+	 */
+	var $operator;	
+	/**
+	 * Number of rows per page
+	 *
+	 * @var int $rowsPerPage
+	 */
+	var $rowsPerPage;
+	/**
+	 * Number of rows we will display on the page
+	 *
+	 * @var int $rowsCount
+	 */
+	var $rowsCount = 0;
+	/**
+	 * Number of rows returned by the query
+	 *
+	 * @var int $rowsTotalCount
+	 */
+	var $rowsTotalCount = 0;
+	/**
+	 * Offset
+	 *
+	 * @var int $offset
+	 */
+	var $offset = 0;
+	/**
+	 * Result handle
+	 *
+	 * @var resource $result
+	 */
+	var $result;
+	/**
+	 * When search by id is enabled, the id to search for
+	 *
+	 * @var int $searchId
+	 */
+	var $searchId = false;
+	/**
+	 * if we want to search for all the words or if only one is sufficient
+	 *
+	 * @var boolean $isExact
+	 */
+	 var $isExact = false;
+	/**
+	 * sections to search in
+	 *
+	 * @var array $sections
+	 */	
+	var $sections = SEARCH__ALL_SECTIONS;
+
+	/**
+	 * Constructor
+	 *
+	 * @param string $words words we are searching for
+	 * @param int $offset offset
+	 * @param boolean $isExact if we want to search for all the words or if only one is sufficient
+	 * @param int $rowsPerPage number of rows per page
+	 */
+	function SearchQuery($words, $offset, $isExact, $rowsPerPage = SEARCH__DEFAULT_ROWS_PER_PAGE) {
+		$this->cleanSearchWords($words);
+		
+		$this->rowsPerPage = $rowsPerPage;
+		$this->offset = $offset;
+		$this->isExact = $isExact;
+		$this->operator = $this->getOperator();
+	}
+	
+	/**
+	 * cleanSearchWords - clean the words we are searching for
+	 *
+	 * @param string $words words we are searching for
+	 */
+	function cleanSearchWords($words) {
+		$words = trim($words);
+		if(!$words) {
+			$this->setError('error_criteria_not_specified');
+			return;
+		}
+		if(is_numeric($words) && $this->implementsSearchById()) {
+			$this->searchId = (int) $words;
+		} else {
+			$words = htmlspecialchars($words);
+			$words = strtr($words, array('%' => '', '_' => ''));
+			$words = preg_replace("/[ \t]+/", ' ', $words);
+			if(strlen($words) < 3) {
+				$this->setError('error_search_minlength');
+				return;
+			}
+			$this->words = explode(' ', quotemeta($words));
+		}
+	}
+	
+	/**
+	 * executeQuery - execute the SQL query to get the results
+	 */ 
+	function executeQuery() {
+		global $sys_use_fti;
+		if($this->searchId) {
+			$query = $this->getSearchByIdQuery();
+		} else {
+			$query = $this->getQuery();
+		}
+
+		if ($sys_use_fti) {
+			db_query("select set_curcfg('default')");
+		}
+		$this->result = db_query(
+			$query,
+			$this->rowsPerPage + 1,
+			$this->offset,
+			SYS_DB_SEARCH
+		);
+
+		$this->rowsTotalCount = db_numrows($this->result);
+		$this->rowsCount = min($this->rowsPerPage, $this->rowsTotalCount);
+	}
+	
+	/**
+	 * getQuery - returns the sql query built to get the search results
+	 * This is an abstract method. It _MUST_ be implemented in children classes.
+	 *
+	 * @return string sql query to execute
+	 */
+	function getQuery() {
+		return;
+	}
+
+	/**
+	 * getIlikeCondition - build the ILIKE condition of the SQL query for a given field name
+	 *
+	 * @param string $fieldName name of the field in the ILIKE condition
+	 * @return string the condition
+	 */
+	function getIlikeCondition($fieldName) {
+		return $fieldName." ILIKE '%" . implode("%' ".$this->operator." ".$fieldName." ILIKE '%", $this->words) ."%'";
+	}
+	
+	/**
+	 * getOperator - get the operator we have to use in ILIKE condition
+	 *
+	 * @return string AND if it is an exact search, OR otherwise
+	 */
+	function getOperator() {
+		if($this->isExact) {
+			return 'AND';
+		} else {
+			return 'OR';
+		}
+	}
+	
+	/**
+	 * implementsSearchById - check if the current object implements the search by id feature by having a getSearchByIdQuery method
+	 *
+	 * @return boolean true if our object implements search by id, false otherwise.
+	 */
+	function implementsSearchById() {
+		return method_exists($this, 'getSearchByIdQuery');
+	}
+	
+	/**
+	 * getResult - returns the result set
+	 *
+	 * @return resource result set
+	 */
+	function & getResult() {
+		return $this->result;
+	}
+	
+	/**
+	 * getRowsCount - returns number of rows for the current page
+	 *
+	 * @return int rows count for the current page
+	 */
+	function getRowsCount() {
+		return $this->rowsCount;
+	}
+	
+	/**
+	 * getRowsTotalCount - returns total number of rows
+	 *
+	 * @return int rows count
+	 */
+	function getRowsTotalCount() {
+		return $this->rowsTotalCount;
+	}
+	
+	/**
+	 * getOffset - returns the offset
+	 *
+	 * @return int offset
+	 */
+	function getOffset() {
+		return $this->offset;
+	}
+	
+	/**
+	 * getRowsPerPage - returns number of rows per page
+	 *
+	 * @return int number of rows per page
+	 */
+	function getRowsPerPage() {
+		return $this->rowsPerPage;
+	}
+	
+	/**
+	 * getWords - returns the array containing words we are searching for
+	 *
+	 * @return array words we are searching for
+	 */
+	function getWords() {
+		return $this->words;
+	}
+	
+	/**
+	 * setSections - set the sections list
+	 *
+	 * @param $sections mixed array of sections or SEARCH__ALL_SECTIONS
+	 */
+	function setSections($sections) {
+		if(is_array($sections)) {
+			//make a comma separated string from the sections array
+			foreach($sections as $key => $section) 
+				$sections[$key] = '\''.$section.'\'';
+			$this->sections = implode(', ', $sections);
+		} else {
+			$this->sections = $sections;
+		}
+	}
+
+	/**
+	 * getFormattedWords - get words formatted in order to be used in the FTI stored procedures
+	 *
+	 * @return string words we are searching for, separated by a pipe
+	 */	
+	function getFormattedWords() {
+		if ($this->isExact) {
+			$words = implode('&', $this->words);
+		} else {
+			$words = implode('|', $this->words);
+		}
+		return $words;
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/search/SkillSearchQuery.class
===================================================================
--- trunk/gforge_base/gforge/common/search/SkillSearchQuery.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/search/SkillSearchQuery.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * GForge Search Engine
+ *
+ * Portions Copyright 1999-2001 (c) VA Linux Systems
+ * The rest Copyright 2004 (c) Guillaume Smet / Open Wide
+ *
+ * http://gforge.org
+ *
+ * @version $Id: SkillSearchQuery.class 5273 2006-02-08 18:47:59Z danper $
+ */
+
+require_once('common/search/SearchQuery.class');
+
+class SkillSearchQuery extends SearchQuery {
+
+	/**
+	 * Constructor
+	 *
+	 * @param string $words words we are searching for
+	 * @param int $offset offset
+	 * @param boolean $isExact if we want to search for all the words or if only one matching the query is sufficient
+	 */
+	function SkillSearchQuery($words, $offset, $isExact) {	
+		$this->SearchQuery($words, $offset, $isExact);	
+	}
+
+	/**
+	 * getQuery - get the sql query built to get the search results
+	 *
+	 * @return string sql query to execute
+	 */
+	function getQuery() {
+		global $sys_use_fti;
+		if ($sys_use_fti) {		
+			$words = $this->getFormattedWords();
+			$sql = "SELECT skills_data.skills_data_id, skills_data.type, headline(skills_data.title, q) as title,
+					skills_data.start,skills_data.finish,headline(skills_data.keywords, q) as keywords
+					FROM skills_data, to_tsquery('$words') AS q, users, skills_data_types, skills_data_idx 
+					WHERE skills_data.user_id=users.user_id AND skills_data.skills_data_id = skills_data_idx.skills_data_id 
+					AND skills_data.type=skills_data_types.type_id AND (vectors @@ q) ORDER BY rank(vectors, q) DESC";
+		} else {
+			$sql = 'SELECT * '
+				. 'FROM skills_data, users, skills_data_types '
+				. 'WHERE (('.$this->getIlikeCondition('skills_data.title', $this->words).') '
+				. 'OR ('.$this->getIlikeCondition('skills_data.keywords', $this->words).')) '
+				. 'AND (skills_data.user_id=users.user_id) '
+				. 'AND (skills_data.type=skills_data_types.type_id) '
+				. 'ORDER BY finish DESC';
+		}
+		return $sql;
+	}
+}
+?>

Added: trunk/gforge_base/gforge/common/search/TasksSearchQuery.class
===================================================================
--- trunk/gforge_base/gforge/common/search/TasksSearchQuery.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/search/TasksSearchQuery.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,115 @@
+<?php
+/**
+ * GForge Search Engine
+ *
+ * Copyright 2004 (c) Dominik Haas, GForge Team
+ *
+ * http://gforge.org
+ *
+ * @version $Id: TasksSearchQuery.class 5273 2006-02-08 18:47:59Z danper $
+ */
+
+require_once('common/search/SearchQuery.class');
+
+class TasksSearchQuery extends SearchQuery {
+	
+	/**
+	* group id
+	*
+	* @var int $groupId
+	*/
+	var $groupId;
+	
+	/**
+	* flag if non public items are returned
+	*
+	* @var boolean $showNonPublic
+	*/	
+	var $showNonPublic;
+	
+	/**
+	 * Constructor
+	 *
+	 * @param string $words words we are searching for
+	 * @param int $offset offset
+	 * @param boolean $isExact if we want to search for all the words or if only one matching the query is sufficient
+	 * @param int $groupId group id
+	 * @param array $sections sections to search in
+	 * @param boolean $showNonPublic flag if private sections are searched too
+	 */
+	function TasksSearchQuery($words, $offset, $isExact, $groupId, $sections=SEARCH__ALL_SECTIONS, $showNonPublic=false) {	
+		$this->groupId = $groupId;
+		$this->showNonPublic = $showNonPublic;
+		
+		$this->SearchQuery($words, $offset, $isExact);
+				
+		$this->setSections($sections);
+	}
+
+	/**
+	 * getQuery - get the sql query built to get the search results
+	 *
+	 * @return string sql query to execute
+	 */
+	function getQuery() {
+		global $sys_use_fti;
+		if ($sys_use_fti) {
+			$sql = 'SELECT project_task.project_task_id,headline(project_task.summary, q) AS summary,project_task.percent_complete,'
+				. ' project_task.start_date,project_task.end_date,users.firstname||\' \'||users.lastname AS realname, project_group_list.project_name, project_group_list.group_project_id ' 
+				. ' FROM project_task, users, project_group_list, project_task_idx, ' 
+				. ' to_tsquery(\''.$this->getFormattedWords().'\') q'
+				. ' WHERE project_task.created_by = users.user_id'
+				. ' AND project_task.project_task_id = project_task_idx.project_task_id'
+				. ' AND project_task.group_project_id = project_group_list.group_project_id '
+				. ' AND project_group_list.group_id  ='.$this->groupId.' ';		
+			if ($this->sections != SEARCH__ALL_SECTIONS) {
+				$sql .= 'AND project_group_list.group_project_id in ('.$this->sections.') ';
+			}
+			if (!$this->showNonPublic) {
+				$sql .= 'AND project_group_list.is_public = 1 ';
+			}
+			$sql .= 'AND (vectors @@q)' 
+				. ' ORDER BY rank(vectors, q) DESC';
+		} else {
+			$sql = 'SELECT project_task.project_task_id,project_task.summary,project_task.percent_complete,'
+				. ' project_task.start_date,project_task.end_date,users.firstname||\' \'||users.lastname AS realname, project_group_list.project_name, project_group_list.group_project_id ' 
+				. ' FROM project_task, users, project_group_list' 
+				. ' WHERE project_task.created_by = users.user_id'
+				. ' AND project_task.group_project_id = project_group_list.group_project_id '
+				. ' AND project_group_list.group_id  ='.$this->groupId.' ';
+			if ($this->sections != SEARCH__ALL_SECTIONS) {
+				$sql .= 'AND project_group_list.group_project_id in ('.$this->sections.') ';
+			}
+			if (!$this->showNonPublic) {
+				$sql .= 'AND project_group_list.is_public = 1 ';
+			}
+			$sql .= 'AND(('.$this->getIlikeCondition('summary', $this->words).')' 
+				. ' OR ('.$this->getIlikeCondition('details', $this->words).'))' 
+				. ' ORDER BY project_group_list.project_name, project_task.project_task_id';
+		}
+		return $sql;
+	}
+	
+	/**
+	 * getSections - returns the list of available subprojects
+	 *
+	 * @param $groupId int group id
+	 * @param $showNonPublic boolean if we should consider non public sections
+	 */
+	function getSections($groupId, $showNonPublic=false) {
+		$sql = 'SELECT group_project_id, project_name FROM project_group_list WHERE group_id = '.$groupId.'';
+		if (!$showNonPublic) {
+			$sql .= ' AND is_public = 1';
+		}
+		$sql .= ' ORDER BY project_name';
+		
+		$sections = array();
+		$res = db_query($sql);
+		while($data = db_fetch_array($res)) {
+			$sections[$data['group_project_id']] = $data['project_name'];
+		}
+		return $sections;
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/search/TrackersSearchQuery.class
===================================================================
--- trunk/gforge_base/gforge/common/search/TrackersSearchQuery.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/search/TrackersSearchQuery.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,153 @@
+<?php
+/**
+ * GForge Search Engine
+ *
+ * Copyright 2004 (c) Dominik Haas, GForge Team
+ *
+ * http://gforge.org
+ *
+ * @version $Id: TrackersSearchQuery.class 5270 2006-02-05 17:12:19Z tperdue $
+ */
+
+require_once('common/search/SearchQuery.class');
+
+class TrackersSearchQuery extends SearchQuery {
+	
+	/**
+	* group id
+	*
+	* @var int $groupId
+	*/
+	var $groupId;
+	
+	/**
+	* flag if non public items are returned
+	*
+	* @var boolean $showNonPublic
+	*/	
+	var $showNonPublic;
+	
+	/**
+	 * Constructor
+	 *
+	 * @param string $words words we are searching for
+	 * @param int $offset offset
+	 * @param boolean $isExact if we want to search for all the words or if only one matching the query is sufficient
+	 * @param int $groupId group id
+	 * @param array $sections sections to search in
+	 * @param boolean $showNonPublic flag if private sections are searched too
+	 */
+	function TrackersSearchQuery($words, $offset, $isExact, $groupId, $sections=SEARCH__ALL_SECTIONS, $showNonPublic=false) {
+		$this->groupId = $groupId;
+		$this->showNonPublic = $showNonPublic;
+		
+		$this->SearchQuery($words, $offset, $isExact);
+
+		$this->setSections($sections);
+	}
+
+	/**
+	 * getQuery - get the sql query built to get the search results
+	 *
+	 * @return string sql query to execute
+	 */
+	function getQuery() {
+		global $sys_use_fti;
+		if ($sys_use_fti) {
+			$nonPublic = 'false';
+			$sections = '';
+			if ($this->showNonPublic) {
+				$nonPublic = '';
+				$nonPublicMsg = '';
+			} else {
+				$nonPublic = 'AND artifact_group_list.is_public = 1';
+				$nonPublicMsg = 'AND agl.is_public = 1';
+			}
+			if ($this->sections != SEARCH__ALL_SECTIONS) {
+				$sections = "AND artifact_group_list.group_artifact_id IN (".$this->sections.")";
+			} else {
+				$sections = '';
+			}
+			$words = $this->getFormattedWords();
+			$group_id=$this->groupId;
+			$sql = "SELECT DISTINCT ON (artifact.artifact_id) artifact.artifact_id,
+			artifact.group_artifact_id, artifact.summary AS summary,
+			artifact.open_date, users.realname, artifact_group_list.name
+			FROM artifact LEFT JOIN artifact_message USING (artifact_id), users,
+			artifact_group_list 
+			WHERE users.user_id = artifact.submitted_by
+			$nonPublic
+			AND artifact_group_list.group_artifact_id = artifact.group_artifact_id
+			AND artifact_group_list.group_id = '$group_id'
+			$sections
+			AND artifact.artifact_id IN (
+
+				SELECT artifact_id FROM artifact_idx, 
+				to_tsquery('$words') AS q WHERE vectors @@ q ORDER BY rank(vectors, q) DESC) 
+
+			OR artifact.artifact_id IN (
+
+				SELECT artifact_id FROM artifact_message_idx, 
+				to_tsquery('$words') AS q WHERE vectors @@ q ORDER BY rank(vectors, q) DESC)";
+		} else {
+			$sql = 'SELECT DISTINCT artifact.artifact_id, artifact.group_artifact_id, artifact.summary, artifact.open_date, users.realname, artifact_group_list.name '
+				. 'FROM artifact LEFT OUTER JOIN artifact_message USING (artifact_id), users, artifact_group_list '
+				. 'WHERE users.user_id = artifact.submitted_by '
+				. 'AND artifact_group_list.group_artifact_id = artifact.group_artifact_id '
+				. 'AND artifact_group_list.group_id = '.$this->groupId.' ';
+			if ($this->sections != SEARCH__ALL_SECTIONS) {
+				$sql .= 'AND artifact_group_list.group_artifact_id in ('.$this->sections.') ';
+			}
+			if (!$this->showNonPublic) {
+				$sql .= 'AND artifact_group_list.is_public = 1 ';
+			}
+			$sql .= 'AND (('.$this->getIlikeCondition('artifact.details', $this->words).') ' 
+				. 'OR ('.$this->getIlikeCondition('artifact.summary', $this->words).') '
+				. 'OR ('.$this->getIlikeCondition('artifact_message.body', $this->words).')) '
+				. 'ORDER BY artifact_group_list.name, artifact.artifact_id';
+		}
+		return $sql;
+	}
+	
+	/**
+	 * getSections - returns the list of available trackers
+	 *
+	 * @param $groupId int group id
+	 * @param $showNonPublic boolean if we should consider non public sections
+	 */
+	function getSections($groupId, $showNonPublic=false) {
+		$sql = 'SELECT group_artifact_id, name FROM artifact_group_list WHERE group_id = '.$groupId.'';
+		if (!$showNonPublic) {
+			$sql .= ' AND artifact_group_list.is_public = 1';
+		}
+		$sql .= ' ORDER BY name';
+		
+		$sections = array();
+		$res = db_query($sql);
+		while($data = db_fetch_array($res)) {
+			$sections[$data['group_artifact_id']] = $data['name'];
+		}
+		return $sections;
+	}
+	
+	function getSearchByIdQuery() {
+		$sql = 'SELECT DISTINCT artifact.artifact_id, artifact.group_artifact_id, artifact.summary, artifact.open_date, users.realname, artifact_group_list.name '
+			. 'FROM artifact LEFT OUTER JOIN artifact_message USING (artifact_id), users, artifact_group_list '
+			. 'WHERE users.user_id = artifact.submitted_by '
+			. 'AND artifact_group_list.group_artifact_id = artifact.group_artifact_id '
+			. 'AND artifact_group_list.group_id = '.$this->groupId.' ';
+		if ($this->sections != SEARCH__ALL_SECTIONS) {
+			$sql .= 'AND artifact_group_list.group_artifact_id in ('.$this->sections.') ';
+		}
+		if (!$this->showNonPublic) {
+			$sql .= 'AND artifact_group_list.is_public = 1 ';
+		}
+		$sql .= 'AND artifact.artifact_id=\''.$this->searchId.'\''
+			. 'ORDER BY artifact_group_list.name, artifact.artifact_id';
+
+
+		return $sql;
+	}
+}
+
+?>

Added: trunk/gforge_base/gforge/common/tracker/Artifact.class
===================================================================
--- trunk/gforge_base/gforge/common/tracker/Artifact.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/tracker/Artifact.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,1402 @@
+<?php
+/**
+ * Artifact.class - Main Artifact class
+ *
+ * Copyright 1999-2001 (c) VA Linux Systems
+ *
+ * @version   $Id: Artifact.class 5527 2006-06-05 20:10:10Z lo-lan-do $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+require_once('common/include/Error.class');
+require_once('common/tracker/ArtifactMessage.class');
+require_once('common/tracker/ArtifactExtraField.class');
+
+// This string is used when sending the notification mail for identifying the
+// user response
+define('ARTIFACT_MAIL_MARKER', '#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+');	
+
+	/**
+	*	Factory method which creates an Artifact from an artifact ID
+	*	
+	*	@param int	The artifact ID
+	*	@param array	The result array, if it's passed in
+	*	@return	object	Artifact object
+	*/
+	function &artifact_get_object($artifact_id,$data=false) {
+		global $ARTIFACT_OBJ;
+		if (!isset($ARTIFACT_OBJ["_".$artifact_id."_"])) {
+			if ($data) {
+				//the db result handle was passed in
+			} else {
+				$res=db_query("SELECT * FROM artifact_vw WHERE artifact_id='$artifact_id'");
+				if (db_numrows($res) <1 ) {
+					$ARTIFACT_OBJ["_".$artifact_id."_"]=false;
+					return false;
+				}
+				$data =& db_fetch_array($res);
+			}
+			$ArtifactType =& artifactType_get_object($data["group_artifact_id"]);
+			$ARTIFACT_OBJ["_".$artifact_id."_"]= new Artifact($ArtifactType,$data);
+		}
+		return $ARTIFACT_OBJ["_".$artifact_id."_"];
+	}	
+
+class Artifact extends Error {
+
+	/**
+	 * Resource ID.
+	 *
+	 * @var		int		$status_res.
+	 */
+	var $status_res;
+
+	/**
+	 * Artifact Type object.
+	 *
+	 * @var		object	$ArtifactType.
+	 */
+	var $ArtifactType; 
+
+	/**
+	 * Array of artifact data.
+	 *
+	 * @var		array	$data_array.
+	 */
+	var $data_array;
+
+	/**
+	 * Array of artifact data for extra fields defined by Admin.
+	 *
+	 * @var		array	$extra_field_data.
+	 */
+	var $extra_field_data;
+
+	/**
+	 * Array of ArtifactFile objects.
+	 *
+	 * @var		array	$files
+	 */
+	var $files; 
+
+	/**
+	 * Database result set of related tasks
+	 *
+	 * @var     result $relatedtasks
+	 */
+	var $relatedtasks;
+    
+	/**
+	 *  Artifact - constructor.
+	 *
+	 *	@param	object	The ArtifactType object.
+	 *  @param	integer	(primary key from database OR complete assoc array) 
+	 *		ONLY OPTIONAL WHEN YOU PLAN TO IMMEDIATELY CALL ->create()
+	 *  @return	boolean	success.
+	 */
+	function Artifact(&$ArtifactType, $data=false) {
+		global $Language;
+		$this->Error(); 
+
+		$this->ArtifactType =& $ArtifactType;
+
+		//was ArtifactType legit?
+		if (!$ArtifactType || !is_object($ArtifactType)) {
+			$this->setError('Artifact: No Valid ArtifactType');
+			return false;
+		}
+
+		//did ArtifactType have an error?
+		if ($ArtifactType->isError()) {
+			$this->setError('Artifact: '.$ArtifactType->getErrorMessage());
+			return false;
+		}
+
+		//
+		//	make sure this person has permission to view artifacts
+		//
+		if (!$this->ArtifactType->userCanView()) {
+			$this->setError($Language->getText('tracker_artifact','private_error'));
+			return false;
+		}
+
+		//
+		//	set up data structures
+		//
+		if ($data) {
+			if (is_array($data)) {
+				$this->data_array =& $data;
+//
+//	Should verify ArtifactType ID
+//
+				return true;
+			} else {
+				if (!$this->fetchData($data)) {
+					return false;
+				} else {
+					return true;
+				}
+			}
+		}
+	}
+
+
+	/**
+	 *	create - construct a new Artifact in the database.
+	 *
+	 *	@param	string	The artifact summary.
+	 *	@param	string	Details of the artifact.
+	 *	@param	int		The ID of the user to which this artifact is to be assigned.
+	 *	@param	int		The artifacts priority.
+	 *	@param	array	Array of extra fields like: array(15=>'foobar',22=>'1');
+	 *  @return id on success / false on failure.
+	 */
+	function create( $summary, $details, $assigned_to=100, $priority=3, $extra_fields=array()) {
+		global $Language;
+		
+		//
+		//	make sure this person has permission to add artifacts
+		//
+		if (!$this->ArtifactType->isPublic()) {
+			//
+			//	Only admins can post/modify private artifacts
+			//
+			if (!$this->ArtifactType->userIsAdmin()) {
+				$this->setError($Language->getText('tracker_artifact','error_admin_modify'));
+				return false;
+			}
+		}
+
+		//
+		//	get the user_id
+		//
+		if (session_loggedin()) {
+			$user=user_getid();
+		} else {
+			if ($this->ArtifactType->allowsAnon()) {
+				$user=100;
+			} else {
+				$this->setError($Language->getText('tracker_artifact','error_no_anonymous'));
+				return false;
+			}
+		}
+
+		//
+		//	data validation
+		//
+		if (!$summary) {
+			$this->setError($Language->getText('tracker_artifact','error_summary_required'));
+			return false;
+		}
+		if (!$details) {
+			$this->setError($Language->getText('tracker_artifact','error_body_required'));
+			return false;
+		}
+		if (!$assigned_to) {
+			$assigned_to=100;
+		}
+		if (!$priority) {
+			$priority=3;
+		}
+//		if (!$status_id) {
+			$status_id=1;		// on creation, status is set to "open"
+//		}
+		//
+		//	They may be using an extra field "status" box so we have to remap
+		//	the status_id based on the extra field - this keeps the counters
+		//	accurate
+		//
+		$status_id=$this->ArtifactType->remapStatus($status_id,$extra_fields);
+		if (!$status_id) {
+			$this->setError($Language->getText('tracker_artifact','error_remap_status'));
+			return false;
+		}
+
+		db_begin();
+
+		//
+		//	Check to see if this user is trying to double-submit
+		//
+		$res=db_query("SELECT * FROM artifact 
+			WHERE summary='".htmlspecialchars($summary)."' 
+			AND submitted_by='$user'
+			AND group_artifact_id='".$this->ArtifactType->getID()."'
+			AND open_date > '". (time() - 86400) ."'");
+		if ($res && db_numrows($res) > 0) {
+			$this->setError($Language->getText('tracker_artifact','double_submit'));
+			db_rollback();
+			return false;
+		}
+
+		$sql="INSERT INTO artifact 
+			(group_artifact_id,status_id,priority,
+			submitted_by,assigned_to,open_date,summary,details) 
+			VALUES 
+			('".$this->ArtifactType->getID()."','$status_id','$priority',
+			'$user','$assigned_to','". time() ."','". htmlspecialchars($summary)."','". htmlspecialchars($details)."')";
+		$res=db_query($sql);
+		if (!$res) {
+			$this->setError('Artifact: '.db_error());
+			db_rollback();
+			return false;
+		}		
+
+		$artifact_id=db_insertid($res,'artifact','artifact_id');
+
+		if (!$res || !$artifact_id) {
+			$this->setError('Artifact: '.db_error());
+			db_rollback();
+			return false;
+		} else {
+			//
+			//	Now set up our internal data structures
+			//
+			if (!$this->fetchData($artifact_id)) {
+				db_rollback();
+				return false;
+			} else {
+				// the changes to the extra fields will be logged in this array.
+				// (we won't use it however)
+				$extra_field_changes = array();
+				if (!$this->updateExtraFields($extra_fields,$extra_field_changes)) {
+					db_rollback();
+					return false;
+				}
+			}
+			//
+			//	now send an email if appropriate
+			//
+			$this->mailFollowup(1);
+			db_commit();
+
+			return $artifact_id;
+		}
+	}
+	
+	/**
+	 *	fetchData - re-fetch the data for this Artifact from the database.
+	 *
+	 *	@param	int		The artifact ID.
+	 *	@return	boolean	success.
+	 */
+	function fetchData($artifact_id) {
+		$res=db_query("SELECT * FROM artifact_vw 
+			WHERE artifact_id='$artifact_id' AND group_artifact_id='".$this->ArtifactType->getID()."'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('Artifact: Invalid ArtifactID');
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	getArtifactType - get the ArtifactType Object this Artifact is associated with.
+	 *
+	 *	@return object	ArtifactType.
+	 */
+	function &getArtifactType() {
+		return $this->ArtifactType;
+	}
+	
+	/**
+	 *	getID - get this ArtifactID.
+	 *
+	 *	@return	int	The artifact_id #.
+	 */
+	function getID() {
+		return $this->data_array['artifact_id'];
+	}
+
+	/**
+	 *	getStatusID - get open/closed/deleted flag.
+	 *
+	 *	@return	int	Status: (1) Open, (2) Closed, (3) Deleted.
+	 */
+	function getStatusID() {
+		return $this->data_array['status_id'];
+	}
+
+	/**
+	 *	getStatusName - get open/closed/deleted text.
+	 *
+	 *	@return	string	The status name.
+	 */
+	function getStatusName() {
+		return $this->data_array['status_name'];
+	}
+
+	/**
+	 *	getPriority - get priority flag.
+	 *
+	 *	@return int priority.
+	 */
+	function getPriority() {
+		return $this->data_array['priority'];
+	}
+
+	/**
+	 *	getSubmittedBy - get ID of submitter.
+	 *
+	 *	@return int user_id of submitter.
+	 */
+	function getSubmittedBy() {
+		return $this->data_array['submitted_by'];
+	}
+
+	/**
+	 *	getSubmittedEmail - get email of submitter.
+	 *
+	 *	@return	string	The email of submitter.
+	 */
+	function getSubmittedEmail() {
+		return $this->data_array['submitted_email'];
+	}
+
+	/**
+	 *	getSubmittedRealName - get real name of submitter.
+	 *
+	 *	@return	string	The real name of submitter.
+	 */
+	function getSubmittedRealName() {
+		return $this->data_array['submitted_realname'];
+	}
+
+	/**
+	 *	getSubmittedUnixName - get login name of submitter.
+	 *
+	 *	@return	string	The unix name of submitter.
+	 */
+	function getSubmittedUnixName() {
+		return $this->data_array['submitted_unixname'];
+	}
+
+	/**
+	 *	getAssignedTo - get ID of assignee.
+	 *
+	 *	@return int user_id of assignee.
+	 */
+	function getAssignedTo() {
+		return $this->data_array['assigned_to'];
+	}
+
+	/**
+	 *	getAssignedEmail - get email of assignee.
+	 *
+	 *	@return	string	The email of assignee.
+	 */
+	function getAssignedEmail() {
+		return $this->data_array['assigned_email'];
+	}
+
+	/**
+	 *	getAssignedRealName - get real name of assignee.
+	 *
+	 *	@return	string	The real name of assignee.
+	 */
+	function getAssignedRealName() {
+		return $this->data_array['assigned_realname'];
+	}
+
+	/**
+	 *	getAssignedUnixName - get login name of assignee.
+	 *
+	 *	@return	string	The unix name of assignee.
+	 */
+	function getAssignedUnixName() {
+		return $this->data_array['assigned_unixname'];
+	}
+
+	/**
+	 *	getOpenDate - get unix time of creation.
+	 *
+	 *	@return int unix time.
+	 */
+	function getOpenDate() {
+		return $this->data_array['open_date'];
+	}
+
+	/**
+	 *	getCloseDate - get unix time of closure.
+	 *
+	 *	@return int unix time.
+	 */
+	function getCloseDate() {
+		return $this->data_array['close_date'];
+	}
+
+	/**
+	 *	  getLastModifiedDate - the last_modified_date of this task.
+	 *
+	 *	  @return int	 the last_modified_date.
+	 */
+	function getLastModifiedDate() {
+		return $this->data_array['last_modified_date'];
+	}
+
+	/**
+	 *	getSummary - get text summary of artifact.
+	 *
+	 *	@return	string The summary (subject).
+	 */
+	function getSummary() {
+		return $this->data_array['summary'];
+	}
+
+	/**
+	 *	getDetails - get text body (message) of artifact.
+	 *
+	 *	@return	string	The body (message).
+	 */
+	function getDetails() {
+		return $this->data_array['details'];
+	}
+
+	/**
+	 *  delete - delete this tracker and all its related data.
+	 *
+	 *  @param  bool	I'm Sure.
+	 *  @return bool true/false;
+	 */
+	function delete($sure) {
+		if (!$sure) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		if (!$this->ArtifactType->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		db_begin();
+		$res = db_query("DELETE FROM artifact_extra_field_data WHERE artifact_id='".$this->getID()."'");
+		if (!$res) {
+			$this->setError('Error deleting extra field data: '.db_error());
+			db_rollback();
+			return false;
+		}
+		$res = db_query("DELETE FROM artifact_file WHERE artifact_id='".$this->getID()."'");
+		if (!$res) {
+			$this->setError('Error deleting file from db: '.db_error());
+			db_rollback();
+			return false;
+		}
+		$res = db_query("DELETE FROM artifact_message WHERE artifact_id='".$this->getID()."'");
+		if (!$res) {
+			$this->setError('Error deleting message: '.db_error());
+			db_rollback();
+			return false;
+		}
+		$res = db_query("DELETE FROM artifact_history WHERE artifact_id='".$this->getID()."'");
+		if (!$res) {
+			$this->setError('Error deleting history: '.db_error());
+			db_rollback();
+			return false;
+		}
+		$res = db_query("DELETE FROM artifact_monitor WHERE artifact_id='".$this->getID()."'");
+		if (!$res) {
+			$this->setError('Error deleting monitor: '.db_error());
+			db_rollback();
+			return false;
+		}
+		$res = db_query("DELETE FROM artifact WHERE artifact_id='".$this->getID()."'");
+		if (!$res) {
+			$this->setError('Error deleting artifact: '.db_error());
+			db_rollback();
+			return false;
+		}
+		
+		if ($this->getStatusID() == 1) {
+			$res = db_query("UPDATE artifact_counts_agg SET count=count-1,open_count=open_count-1
+				WHERE group_artifact_id='".$this->getID()."'");
+			if (!$res) {
+				$this->setError('Error updating artifact_counts_agg (1): '.db_error());
+				db_rollback();
+				return false;
+			}
+		} elseif ($this->getStatusID() == 2) {
+			$res = db_query("UPDATE artifact_counts_agg SET count=count-1
+				WHERE group_artifact_id='".$this->getID()."'");
+			if (!$res) {
+				$this->setError('Error updating artifact_counts_agg (2): '.db_error());
+				db_rollback();
+				return false;
+			}
+		}
+
+		db_commit();
+		return true;
+	}
+
+	/**
+	 *  setMonitor - user can monitor this artifact.
+	 *
+	 *  @return false - always false - always use the getErrorMessage() for feedback
+	 */
+	function setMonitor() {
+		global $Language;
+		if (session_loggedin()) {
+
+			$user_id=user_getid();
+			$user =& user_get_object(user_getid());
+			$email=' ';
+
+			//we don't want to include the "And email=" because
+			//a logged-in user's email may have changed
+			$email_sql='';
+
+		} else {
+
+			$this->setError($Language->getText('tracker_artifact','error_valid_email_required'));
+			return false;
+
+		}
+
+		$res=db_query("SELECT * FROM artifact_monitor 
+			WHERE artifact_id='". $this->getID() ."' 
+			AND user_id='$user_id'");
+
+		if (!$res || db_numrows($res) < 1) {
+			//not yet monitoring
+			$res=db_query("INSERT INTO artifact_monitor (artifact_id,user_id) 
+				VALUES ('". $this->getID() ."','$user_id')");
+			if (!$res) {
+				$this->setError(db_error());
+				return false;
+			} else {
+				$this->setError($Language->getText('tracker_artifact','monitoring_activated'));
+				return false;
+			}
+		} else {
+			//already monitoring - remove their monitor
+			db_query("DELETE FROM artifact_monitor 
+				WHERE artifact_id='". $this->getID() ."' 
+				AND user_id='$user_id'");
+			$this->setError($Language->getText('tracker_artifact','monitoring_deactivated'));
+			return false;
+		}
+	}
+
+	function isMonitoring() {
+		if (!session_loggedin()) {
+			return false;
+		}
+		$sql="SELECT count(*) FROM artifact_monitor WHERE user_id='".user_getid()."' AND artifact_id='".$this->getID()."';";
+		$result = db_query($sql);
+		$row_count = db_fetch_array($result);
+		return $result && $row_count['count'] > 0;
+	}
+
+	/**
+	 *  getMonitorIds - array of email addresses monitoring this Artifact.
+	 *
+	 *  @return array of email addresses monitoring this Artifact.
+	 */
+	function &getMonitorIds() {
+		$res=db_query("SELECT user_id
+			FROM artifact_monitor 
+			WHERE artifact_id='". $this->getID() ."'");
+		return array_unique(array_merge($this->ArtifactType->getMonitorIds(),util_result_column_to_array($res)));
+	}
+
+	/**
+	 *	getHistory - returns a result set of audit trail for this support request.
+	 *
+	 *	@return database result set.
+	 */
+	function getHistory() {
+		$sql="SELECT * ".
+		"FROM artifact_history_user_vw ".
+		"WHERE artifact_id='". $this->getID() ."' ".
+		"ORDER BY entrydate DESC";
+		return db_query($sql);
+	}
+
+	/**
+	 *	getMessages - get the list of messages attached to this artifact.
+	 *
+	 *	@return database result set.
+	 */
+	function getMessages() {
+		$sql="select * ".
+			"FROM artifact_message_user_vw ".
+			"WHERE artifact_id='". $this->getID() ."' ORDER BY adddate DESC";
+		return db_query($sql);
+	}
+
+	/**
+	 *	getMessageObjects - get an array of message objects.
+	 *
+	 *	@return array Of ArtifactMessage objects.
+	 */
+	function &getMessageObjects() {
+		$res=$this->getMessages();
+		$return = array();
+		while ($arr = db_fetch_array($res)) {
+			//$return[]=new ArtifactMessage($arr['artifact_id'],$arr);
+			$return[] = new ArtifactMessage($this, $arr);
+		}
+		return $return;
+	}
+
+	/**
+	 *	getFiles - get array of ArtifactFile's.
+	 *
+	 *	@return array of ArtifactFile's.
+	 */
+	function &getFiles() {
+		if (!isset($this->files)) {
+			$sql="select * ".
+			"FROM artifact_file_user_vw ".
+			"WHERE artifact_id='". $this->getID() ."'";
+			$res=db_query($sql);
+			$rows=db_numrows($res);
+			if ($rows > 0) {
+				for ($i=0; $i < $rows; $i++) {
+					$this->files[$i]=new ArtifactFile($this,db_fetch_array($res));
+				}
+			} else {
+				$this->files=array();
+			}
+		}
+		return $this->files;
+	}
+
+	/**
+	 * getRelatedTasks - get array of related tasks
+	 *
+	 * @return Database result set
+	 */
+	function getRelatedTasks() {
+		if (!$this->relatedtasks) {
+			$this->relatedtasks=
+			db_query("SELECT pt.group_project_id,pt.project_task_id,pt.summary,pt.start_date,pt.end_date,pgl.group_id
+			FROM project_task pt, project_group_list pgl
+			WHERE pt.group_project_id = pgl.group_project_id AND
+			EXISTS (SELECT project_task_id FROM project_task_artifact
+				WHERE project_task_id=pt.project_task_id
+				AND artifact_id = ". $this->getID() . ")");
+		}
+		return $this->relatedtasks;
+	}
+
+	/**
+	 *  addMessage - attach a text message to this Artifact.
+	 *
+	 *	@param	string	The message being attached.
+	 *	@param	string	Email address of message creator.
+	 *	@param	bool	Whether to email out a followup.
+	 *	@access private.
+	 *  @return	boolean	success.
+	 */
+	function addMessage($body,$by=false,$send_followup=false) {
+		global $Language;
+		if (!$body) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		if (session_loggedin()) {
+			$user_id=user_getid();
+			$user =& user_get_object($user_id);
+			if (!$user || !is_object($user)) {
+				$this->setError('ERROR - Logged In User Bug Could Not Get User Object');
+				return false;
+			}
+			//	we'll store this email even though it will likely never be used - 
+			//	since we have their correct user_id, we can join the USERS table to get email
+			$by=$user->getEmail();
+		} elseif (!$this->ArtifactType->allowsAnon()) {
+			$this->setError($Language->getText('tracker_artifact','error_no_anonymous'));
+			return false;
+		} else {
+			$user_id=100;
+			if (!$by || !validate_email($by)) {
+				$this->setMissingParamsError();
+				return false;
+			}
+		}
+
+		$sql="insert into artifact_message (artifact_id,submitted_by,from_email,adddate,body) ".
+			"VALUES ('". $this->getID() ."','$user_id','$by','". time() ."','". htmlspecialchars($body). "')";
+		$res = db_query($sql);
+		if ($send_followup) {
+			$this->mailFollowup(2,false);
+		}
+		return $res;
+	}
+
+	/**
+	 *  addHistory - add an entry to audit trail.
+	 *
+	 *  @param	string	The name of the field in the database being modified.
+	 *  @param	string	The former value of this field.
+	 *  @access private.
+	 *  @return	boolean	success.
+	 */
+	function addHistory($field_name,$old_value) {
+		if (!session_loggedin()) {
+			$user=100;
+		} else {
+			$user=user_getid();
+		}
+		$sql="insert into artifact_history(artifact_id,field_name,old_value,mod_by,entrydate) 
+			VALUES ('". $this->getID() ."','$field_name','".addslashes($old_value)."','$user','". time() ."')";
+		return db_query($sql);
+	}
+
+	/**
+	 *	update - update the fields in this artifact.
+	 *
+	 *	@param	int		The artifact priority.
+	 *	@param	int		The artifact status ID.
+	 *	@param	int		The person to which this artifact is to be assigned.
+	 *	@param	string	The artifact summary.
+	 *	@param	int		The canned response.
+	 *	@param	string	Attaching another comment.
+	 *	@param	int		Allows you to move an artifact to another type.
+	 *	@param	array	Array of extra fields like: array(15=>'foobar',22=>'1');
+	 *	@return	boolean	success.
+	 */
+	function update($priority,$status_id,
+		$assigned_to,$summary,$canned_response,$details,$new_artifact_type_id,$extra_fields=array()) {
+
+		global $Language;
+		/*
+			Field-level permission checking
+		*/
+		if ($this->ArtifactType->userIsAdmin()) {
+			//admin can do everything
+		} else {
+			//everyone else cannot modify these fields
+			$priority=$this->getPriority();
+			$summary=addslashes($this->getSummary());
+			$canned_response=100;
+			$new_artifact_type_id=$this->ArtifactType->getID();
+			$assigned_to=$this->getAssignedTo();
+
+			if ($this->ArtifactType->userIsTechnician()) {
+				//technician can update only certain fields
+				//which were not overridden above
+			} else {
+				//submitter can no longer call this function
+				$this->setPermissionDeniedError();
+				return false;
+			}
+
+		}
+		//
+		//	They may be using an extra field "status" box so we have to remap
+		//	the status_id based on the extra field - this keeps the counters
+		//	accurate
+		//
+		if (count($extra_fields) > 0) {
+			$status_id=$this->ArtifactType->remapStatus($status_id,$extra_fields);
+		}
+		if (!$this->getID() 
+			|| !$assigned_to 
+			|| !$status_id 
+			|| !$canned_response 
+			|| !$new_artifact_type_id) {
+			$this->setMissingParamsError();
+			return false;
+		}
+
+
+		// Array to record which properties were changed
+		$changes = array();
+
+		db_begin();
+
+		//
+		//	Get a lock on this row in the database
+		//
+		$lock=db_query("SELECT * FROM artifact WHERE artifact_id='".$this->getID()."' FOR UPDATE");
+		$artifact_type_id = $this->ArtifactType->getID();
+		//
+		//	Attempt to move this Artifact to a new ArtifactType
+		//	need to instantiate new ArtifactType obj and test perms
+		//
+		if ($new_artifact_type_id != $artifact_type_id) {
+			$newArtifactType= new ArtifactType($this->ArtifactType->getGroup(), $new_artifact_type_id);
+			if (!is_object($newArtifactType) || $newArtifactType->isError()) {
+				$this->setError('Artifact: Could not move to new ArtifactType'. $newArtifactType->getErrorMessage());
+				db_rollback();
+				return false;
+			}
+			//	do they have perms for new ArtifactType?
+			if (!$newArtifactType->userIsAdmin()) {
+				$this->setPermissionDeniedError();
+				db_rollback();
+				return false;
+			}
+			
+			//
+			//	Now set Assigned to 100 in the new ArtifactType
+			//
+			$status_id=1;
+			$assigned_to='100';
+			//can't send a canned response when changing ArtifactType
+			$canned_response=100;
+			$this->ArtifactType =& $newArtifactType;
+			$update = true;
+
+			//
+			//      This is a major problem - the extra fields
+			//      are completely different IDs, and may not even
+			//      exist in the new tracker. All extra_fields will be deleted and
+			//      then set to 100 in the new tracker.
+			//
+			$res=db_query("DELETE FROM artifact_extra_field_data WHERE artifact_id='".$this->getID()."'");
+			$extra_fields=array();
+
+		}
+		
+		
+
+		$sqlu='';
+
+		//
+		//	handle audit trail & build SQL statement
+		//
+		if ($this->getStatusID() != $status_id) {
+			$this->addHistory('status_id',$this->getStatusID());
+			$sqlu .= " status_id='$status_id', ";
+			$changes['status'] = 1;
+			$update = true;
+		}
+		if ($this->getPriority() != $priority) {
+			$this->addHistory('priority',$this->getPriority());
+			$sqlu .= " priority='$priority', ";
+			$changes['priority'] = 1;
+			$update = true;
+		}
+
+		if ($this->getAssignedTo() != $assigned_to) {
+			$this->addHistory('assigned_to',$this->getAssignedTo());
+			$sqlu .= " assigned_to='$assigned_to', ";
+			$changes['assigned_to'] = 1;
+			$update = true;
+		}
+		if ($summary && (addslashes($this->getSummary()) != htmlspecialchars($summary))) {
+			$this->addHistory('summary', addslashes($this->getSummary()));
+			$sqlu .= " summary='". htmlspecialchars($summary) ."', ";
+			$changes['summary'] = 1;
+			$update = true;
+		}
+
+		if ($details) {
+			$this->addMessage($details,'',0);
+			$changes['details'] = 1;
+			$send_message=true;
+		}
+
+		//
+		//	Enter the timestamp if we are changing to closed
+		//
+		if ($status_id != 1) {
+			$now=time();
+			$sqlu .= " close_date='$now', ";
+			$this->addHistory('close_date',$now);
+			$update = true;
+		}
+
+		/*
+			Finally, update the artifact itself
+		*/
+		if ($update){
+			$sql = "UPDATE artifact 
+				SET 
+				$sqlu
+				group_artifact_id='$new_artifact_type_id'
+				WHERE 
+				artifact_id='". $this->getID() ."'
+				AND group_artifact_id='$artifact_type_id'";
+			$result=db_query($sql);
+
+			if (!$result || db_affected_rows($result) < 1) {
+				$this->setError('Error - update failed!'.db_error());
+				db_rollback();
+				return false;
+			} else {
+				if (!$this->fetchData($this->getID())) {
+					db_rollback();
+					return false;
+				}
+			}
+		}
+
+		//extra field handling
+		$update=true;
+		if (!$this->updateExtraFields($extra_fields,$changes)) {
+//TODO - see if anything actually did change
+			db_rollback();
+			return false;
+		}
+		
+		/*
+			handle canned responses
+
+			Instantiate ArtifactCanned and get the body of the message
+		*/
+		if ($canned_response != 100) {
+			//don't care if this response is for this group - could be hacked
+			$acr=new ArtifactCanned($this->ArtifactType,$canned_response);
+			if (!$acr || !is_object($acr)) {
+				$this->setError('Artifact: Could Not Create Canned Response Object');
+			} elseif ($acr->isError()) {
+				$this->setError('Artifact: '.$acr->getErrorMessage());
+			} else {
+				$body = addslashes($acr->getBody());
+				if ($body) {
+					if (!$this->addMessage(util_unconvert_htmlspecialchars($body),'',0)) {
+						db_rollback();
+						return false;
+					} else {
+						$send_message=true;
+					}
+				} else {
+					$this->setError('Artifact: Unable to Use Canned Response');
+					return false;
+				}
+			}
+		}
+
+		if ($update || $send_message){
+			/*
+				now send the email
+			*/
+			$this->mailFollowup(2, false, $changes);
+			db_commit();
+			return true;
+		} else {
+			//nothing changed, so cancel the transaction
+			$this->setError($Language->getText('tracker_artifact','nothing_changed'));
+			db_rollback();
+			return false;
+		}
+	
+	}
+
+	/**
+	 * 	updateExtraFields - updates the extra data elements for this artifact
+	 *	e.g. the extra fields created and defined by the admin.
+	 *
+	 *	@param	array	Array of extra fields like: array(15=>'foobar',22=>'1');
+	 *	@param	array	Array where changes to the extra fields should be logged
+	 *	@return true on success / false on failure
+	 */
+	function updateExtraFields($extra_fields,&$changes){
+/*
+	This is extremely complex code - we have take the passed array
+	and see if we need to insert it into the db, and may have to 
+	add history rows for the audit trail
+
+	start by getting all the available extra fields from ArtifactType
+		For each field from ArtifacType, check the passed array - 
+			This prevents someone from passing bogus extra field entries - they will be ignored
+			if the passed entry is blank, may have to force a default value
+			if the passed array is different from the existing data in db, 
+				delete old entry and insert new entries, along with possible audit trail
+			else
+				skip it and continue to next item
+
+*/
+		if (empty($extra_fields)) {
+			return true;
+		}
+		//get a list of extra fields for this artifact_type
+		$ef = $this->ArtifactType->getExtraFields();
+		$efk=array_keys($ef);
+
+		//now we'll update this artifact for each extra field
+		for ($i=0; $i<count($efk); $i++) {
+			$efid=$efk[$i];
+			$type=$ef[$efid]['field_type'];
+
+//
+//	Force each field to have some value if it is a numeric field
+//	text fields will just be purged and skipped
+//
+			if (!array_key_exists($efid, $extra_fields) || !$extra_fields[$efid]) {
+				if ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
+					$this->setError('Status Custom Field Must Be Set');
+					return false;
+				} elseif (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_RADIO)) {
+					$extra_fields[$efid]='100';
+				} elseif (($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX)) {
+					$extra_fields[$efid]=array('100');
+				} else {
+					$resdel=db_query("DELETE FROM artifact_extra_field_data
+					WHERE
+					artifact_id='".$this->getID()."'
+					AND extra_field_id='".$efid."'");
+					continue;
+				}
+			}
+			//
+			//	get the old rows of data
+			//
+			$resd=db_query("SELECT * FROM artifact_extra_field_data
+				WHERE
+				artifact_id='".$this->getID()."'
+				AND extra_field_id='".$efid."'");
+			$rows=db_numrows($resd);
+			if ($resd && $rows) {
+//
+//POTENTIAL PROBLEM - no entry was there before, but adding one now - may need history
+//
+				//
+				//	Compare for history purposes
+				//
+				
+				// these types have arrays associated to them, so they need
+				// special handling to check for differences
+				if ($type == ARTIFACT_EXTRAFIELDTYPE_MULTISELECT || $type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) {
+					// check the differences between the old values and the new values
+					$old_values = util_result_column_to_array($resd,"field_data");
+					
+					$added_values = array_diff($extra_fields[$efid], $old_values);
+					$deleted_values = array_diff($old_values, $extra_fields[$efid]);
+					
+					if (!empty($added_values) || !empty($deleted_values))	{	// there are differences...
+						$field_name = $ef[$efid]['field_name'];
+						$changes["extra_fields"][$efid] = 1;
+						
+						// Do a history entry only for deleted values
+						if (!empty($deleted_values)) {
+							$this->addHistory($field_name, $this->ArtifactType->getElementName($deleted_values));
+						}
+						
+
+						$resdel=db_query("DELETE FROM artifact_extra_field_data
+						WHERE
+						artifact_id='".$this->getID()."'
+						AND extra_field_id='".$efid."'");
+					} else {
+						continue;
+					}
+				} elseif (addslashes(db_result($resd,0,'field_data')) == htmlspecialchars($extra_fields[$efid])) {
+					//element did not change
+					continue;
+				} else {
+					//element DID change - do a history entry
+					$field_name = $ef[$efid]['field_name'];
+					$changes["extra_fields"][$efid] = 1;
+					$resdel=db_query("DELETE FROM artifact_extra_field_data
+					WHERE
+					artifact_id='".$this->getID()."'
+					AND extra_field_id='".$efid."'");
+					if (($type == ARTIFACT_EXTRAFIELDTYPE_SELECT) || ($type == ARTIFACT_EXTRAFIELDTYPE_RADIO) || ($type == ARTIFACT_EXTRAFIELDTYPE_STATUS)) {
+//don't add history for text fields
+						$this->addHistory($field_name,$this->ArtifactType->getElementName(db_result($resd,0,'field_data')));
+					}
+				}
+			} else {
+
+//no history for this extra field exists
+
+			}
+			//
+			//	See if anything was even passed for this extra_field_id
+			//
+			if (!$extra_fields[$efid]) {
+				//nothing in field to update - text fields may be blank
+			} else {
+				//determine the type of field and whether it should have multiple rows supporting it
+				$type=$ef[$efid]['field_type'];
+				if (($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) || ($type==ARTIFACT_EXTRAFIELDTYPE_MULTISELECT)) {
+					$multi_rows=true;
+					$count=count($extra_fields[$efid]);
+					for ($fin=0; $fin<$count; $fin++) {
+						$sql="INSERT INTO artifact_extra_field_data (artifact_id,extra_field_id,field_data) 
+							values ('".$this->getID()."','".$efid."',
+							'".$extra_fields[$efid][$fin]."')";
+						$res=db_query($sql);
+						if (!$res) {
+							$this->setError('Artifact::updateExtraFields:: '.$sql.db_error());
+							return false;
+						}
+					}
+				} else {
+					$multi_rows=false;
+					$count=1;
+					$res=db_query("INSERT INTO artifact_extra_field_data (artifact_id,extra_field_id,field_data) 
+						values ('".$this->getID()."','".$efid."',
+						'".htmlspecialchars($extra_fields[$efid])."')");
+					if (!$res) {
+						$this->setError('Artifact::updateExtraFields:: '.db_error());
+						return false;
+					}
+				}
+			}
+		}
+		unset($this->extra_field_data);
+		return true;
+	}
+
+	/**
+	 *	getExtraFieldData - get an array of data for the extra fields associated with this artifact
+	 *
+	 *	@return	array	array of data
+	 */
+	function &getExtraFieldData() {
+		if (!isset($this->extra_field_data)) {
+			$this->extra_field_data = array();
+			$res=db_query("SELECT * FROM artifact_extra_field_data 
+				WHERE artifact_id='".$this->getID()."' ORDER BY extra_field_id");
+			$ef = $this->ArtifactType->getExtraFields();
+			while ($arr = db_fetch_array($res)) {
+				$type=$ef[$arr['extra_field_id']]['field_type'];
+				if (($type == ARTIFACT_EXTRAFIELDTYPE_CHECKBOX) || ($type==ARTIFACT_EXTRAFIELDTYPE_MULTISELECT)) {
+					//accumulate a sub-array of values in cases where you may have multiple rows
+					if (!is_array($this->extra_field_data[$arr['extra_field_id']])) {
+						$this->extra_field_data[$arr['extra_field_id']] = array();
+					}
+					$this->extra_field_data[$arr['extra_field_id']][]=$arr['field_data'];
+				} else {
+					$this->extra_field_data[$arr['extra_field_id']]=$arr['field_data'];
+				}
+			}
+		}
+		return $this->extra_field_data;
+	}
+
+	/**
+	 *	marker - adds the > symbol to fields that have been modified for the email message
+	 *
+	 *
+	 */
+	function marker($prop_name,$changes,$extra_field_id=0) {
+		if ($prop_name == 'extra_fields' && $changes[$prop_name][$extra_field_id]) {
+			return '>';
+		} else if ($prop_name != 'extra_fields' && $changes[$prop_name]) {
+			return '>';
+		} else {
+			return '';
+		}
+	}				
+
+	/**
+	 *	mailFollowup - send out an email update for this artifact.
+	 *
+	 *	@param	int		(1) initial/creation (2) update.
+	 *	@param	array	Array of additional addresses to mail to.
+	 *	@param	array	Array of fields changed in this update .
+	 *	@access private.
+	 *	@return	boolean	success.
+	 */
+	function mailFollowup($type, $more_addresses=false, $changes='') {
+		global $sys_datefmt;
+
+		if (!$changes) {
+			$changes=array();
+		}
+		
+		$body = $this->ArtifactType->getName() ." item #". $this->getID() .", was opened at ". date( $sys_datefmt, $this->getOpenDate() ). 
+			"\nYou can respond by visiting: ".
+			"\nhttp://".$GLOBALS['sys_default_domain']."/tracker/?func=detail&atid=". $this->ArtifactType->getID() .
+				"&aid=". $this->getID() .
+				"&group_id=". $this->ArtifactType->Group->getID() .
+			"\nOr by replying to this e-mail entering your response between the following markers: ".
+			"\n".ARTIFACT_MAIL_MARKER.
+			"\n(enter your response here)".
+			"\n".ARTIFACT_MAIL_MARKER.
+			"\n\n".
+			$this->marker('status',$changes).
+			 "Status: ". $this->getStatusName() ."\n".
+			$this->marker('priority',$changes).
+			 "Priority: ". $this->getPriority() ."\n".
+			"Submitted By: ". $this->getSubmittedRealName() .
+			" (". $this->getSubmittedUnixName(). ")"."\n".
+			$this->marker('assigned_to',$changes).
+			 "Assigned to: ". $this->getAssignedRealName() .
+			 " (". $this->getAssignedUnixName(). ")"."\n".
+			$this->marker('summary',$changes).
+			 "Summary: ". util_unconvert_htmlspecialchars( $this->getSummary() )." \n";
+			 
+		// Now display the extra fields
+		$efd = $this->getExtraFieldDataText();
+		foreach ($efd as $efid => $ef) {
+			$body .= $this->marker('extra_fields', $changes, $efid);
+			$body .= $ef["name"].": ".$ef["value"]."\n";
+		}
+			
+		$subject='['. $this->ArtifactType->Group->getUnixName() . '-' . $this->ArtifactType->getName() . '][' . $this->getID() .'] '. util_unconvert_htmlspecialchars( $this->getSummary() );
+
+		if ($type > 1) {
+			// get all the email addresses that are monitoring this request or the ArtifactType
+			$monitor_ids =& $this->getMonitorIds();
+		} else {
+			// initial creation, we just get the users monitoring the ArtifactType
+			$monitor_ids =& $this->ArtifactType->getMonitorIds();
+		}
+
+		if ($more_addresses) {
+			$emails[] = $more_addresses;
+		}
+		//we don't email the current user
+		if ($this->getAssignedTo() != user_getid()) {
+			$monitor_ids[] = $this->getAssignedTo();
+		}
+		if ($this->getSubmittedBy() != user_getid()) {
+			$monitor_ids[] = $this->getSubmittedBy();
+		}
+		//initial submission
+		if ($type==1) {
+			//if an email is set for this ArtifactType
+			//add that address to the BCC: list
+			if ($this->ArtifactType->getEmailAddress()) {
+				$emails[] = $this->ArtifactType->getEmailAddress();
+			}
+		} else {
+			//update
+			if ($this->ArtifactType->emailAll()) {
+				$emails[] = $this->ArtifactType->getEmailAddress();
+			}
+		}
+
+		$body .= "\n\nInitial Comment:".
+			"\n".util_unconvert_htmlspecialchars( $this->getDetails() ) .
+			"\n\n----------------------------------------------------------------------";
+
+		if ($type > 1) {
+			/*
+				Now include the followups
+			*/
+			$result2=$this->getMessages();
+
+			$rows=db_numrows($result2);
+		
+			if ($result2 && $rows > 0) {
+				for ($i=0; $i<$rows; $i++) {
+					//
+					//	for messages posted by non-logged-in users, 
+					//	we grab the email they gave us
+					//
+					//	otherwise we use the confirmed one from the users table
+					//
+					if (db_result($result2,$i,'user_id') == 100) {
+						$emails[] = db_result($result2,$i,'from_email');
+					} else {
+						$monitor_ids[] = db_result($result2,$i,'user_id');
+					}
+
+
+					$body .= "\n\n";
+					if ($i == 0) {
+						$body .= $this->marker('details',$changes);
+					}
+					$body .= "Comment By: ". db_result($result2,$i,'realname') . " (".db_result($result2,$i,'user_name').")".
+					"\nDate: ". date( $sys_datefmt,db_result($result2,$i,'adddate') ).
+					"\n\nMessage:".
+					"\n".util_unconvert_htmlspecialchars( db_result($result2,$i,'body') ).
+					"\n\n----------------------------------------------------------------------";
+				}	   
+			}
+
+		}
+
+		$body .= "\n\nYou can respond by visiting: ".
+		"\nhttp://".$GLOBALS['sys_default_domain']."/tracker/?func=detail&atid=". $this->ArtifactType->getID() .
+			"&aid=". $this->getID() .
+			"&group_id=". $this->ArtifactType->Group->getID();
+
+		//only send if some recipients were found
+		if (count($emails) < 1 && count($monitor_ids) < 1) {
+			return true;
+		}
+
+		if (count($monitor_ids) < 1) {
+			$monitor_ids=array();
+		} else {
+			$monitor_ids=array_unique($monitor_ids);
+		}
+		
+		$from = $this->ArtifactType->getReturnEmailAddress();
+		$extra_headers = 'Reply-to: '.$from;
+		
+		// load the e-mail addresses of the users
+		$users =& user_get_objects($monitor_ids);
+		if (count($users) > 0) {
+			foreach ($users as $user) {
+				if ($user->getStatus() == "A") { //we are only sending emails to active users
+					$emails[] = $user->getEmail();
+				}
+			}
+		}
+		
+//		print($body);
+
+		//now remove all duplicates from the email list
+		if (count($emails) > 0) {
+			$BCC=implode(',',array_unique($emails));
+			util_send_message('',$subject,$body,$from,$BCC,'',$extra_headers);			
+		}
+		
+		
+		//util_handle_message($monitor_ids,$subject,$body,$BCC);
+		
+		return true;
+	}
+	
+	/**
+	* getExtraFieldDataText - Return the extra fields' data in a human-readable form.
+	*
+	* @return array Array containing field ID => field name and value associated to it for
+	*	this artifact
+	*/
+	function getExtraFieldDataText() {
+		global $Language;
+
+		// First we get the list of extra fields and the data
+		// associated to the fields
+		$efs = $this->ArtifactType->getExtraFields();
+		$efd = $this->getExtraFieldData();
+		
+		$return = array();
+
+		foreach ($efs as $efid => $ef) {
+			$name = $ef["field_name"];
+			
+			// Get the value according to the type
+			switch ($ef["field_type"]) {
+				
+			// for these types, the associated value comes straight
+			case ARTIFACT_EXTRAFIELDTYPE_TEXT:
+			case ARTIFACT_EXTRAFIELDTYPE_TEXTAREA:
+				$value = $efd[$efid];
+				break;
+
+			// the other types have and ID or an array of IDs associated to them
+			default:
+				$value = $this->ArtifactType->getElementName($efd[$efid]);
+			}
+			
+			$return[$efid] = array("name" => $name, "value" => $value);
+		}
+		
+		return $return;
+	}
+
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/tracker/ArtifactBoxOptions.class
===================================================================
--- trunk/gforge_base/gforge/common/tracker/ArtifactBoxOptions.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/tracker/ArtifactBoxOptions.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,216 @@
+<?php
+/**
+ * ArtifactSelectionBox.class - Class to handle user defined artifacts
+ *
+ * Copyright 2004 (c) Anthony J. Pugliese
+ *
+ * @version   $Id: ArtifactBoxOptions.class 5527 2006-06-05 20:10:10Z lo-lan-do $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+require_once('common/include/Error.class');
+
+class ArtifactBoxOptions extends Error {
+
+	/** 
+	 * The artifact type object.
+	 *
+	 * @var		object	$ArtifactType.
+	 */
+	var $ArtifactType; //object
+
+	/**
+	 * Array of artifact data.
+	 *
+	 * @var		array	$data_array.
+	 */
+	var $data_array;
+	/**
+	 *	ArtifactSelectionBox - Constructer
+	 *
+	 *	@param	object	ArtifactType object.
+	 *  @param	array	(all fields from artifact_file_user_vw) OR id from database.
+	 *  @return	boolean	success.
+	 */
+	function ArtifactBoxOptions(&$ArtifactType,$data=false) {
+		$this->Error(); 
+		
+		//was ArtifactType legit?
+		if (!$ArtifactType || !is_object($ArtifactType)) {
+			$this->setError('ArtifactSelectionBox: No Valid ArtifactType');
+			return false;
+		}
+		//did ArtifactType have an error?
+		if ($ArtifactType->isError()) {
+			$this->setError('ArtifactSelectionBox: '.$Artifact->getErrorMessage());
+			return false;
+		}
+		$this->ArtifactType =& $ArtifactType;
+		if ($data) {
+			if (is_array($data)) {
+				$this->data_array =& $data;
+				return true;
+			} else {
+				if (!$this->fetchData($data)) {
+					return false;
+				} else {
+					return true;
+				}
+			}
+		}
+	}
+
+	/**
+	 *	create - create a new row in the table used to store the 
+	 *	choices for selection boxes.  This function is only used for 
+	 *	extra fields and boxes configured by the admin
+	 *
+	 *	@param	string		Name of the choice
+	 *	@param	int		Id the box that contains the choice.
+	 *  @return 	true on success / false on failure.
+	 */
+	
+	function create($name,$id) {
+		global $Language;
+//settype($id,"integer");	
+		//
+		//	data validation
+		//
+		if (!$name) {
+			$this->setError($Language->getText('tracker_admin_build_boxes','required_choice_name'));
+			return false;
+		}
+		if (!$this->ArtifactType->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		$sql="INSERT INTO artifact_group_selection_box_options (artifact_box_id,box_options_name) 
+			VALUES ('$id','".htmlspecialchars($name)."')";
+
+		$result=db_query($sql);
+		if ($result && db_affected_rows($result) > 0) {
+			$this->clearError();
+			return true;
+		} else {
+			$this->setError(db_error());
+			return false;
+		}
+
+
+			//
+			//	Now set up our internal data structures
+			//
+			if (!$this->fetchData($id)) {
+				return false;
+			}
+
+	}
+
+
+	/**
+	 *	fetchData - re-fetch the data for this ArtifactBoxOptions from the database.
+	 *
+	 *	@param	int		ID of the Box.
+	 *	@return	boolean	success.
+	 */
+	function fetchData($id) {
+		$res=db_query("SELECT * FROM artifact_group_selection_box_options WHERE id='$id'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('ArtifactSelectionBox: Invalid Artifact ID');
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	getArtifactType - get the ArtifactType Object this ArtifactSelectionBox is associated with.
+	 *
+	 *	@return object	ArtifactType.
+	 */
+	function &getArtifactType() {
+		return $this->ArtifactType;
+	}
+	
+	/**
+	 *	getID - get this ArtifactSelectionBox ID.
+	 *
+	 *	@return	int	The id #.
+	 */
+	function getID() {
+		return $this->data_array['id'];
+	}
+	
+	/**
+	 *	getBoxID - get this  artifact box id.
+	 *
+	 *	@return	int	The id #.
+	 */
+	function getBoxID() {
+		return $this->data_array['artifact_box_id'];
+	}
+
+	/**
+	 *	getName - get the name.
+	 *
+	 *	@return	string	The name.
+	 */
+	function getName() {
+		return $this->data_array['box_options_name'];
+	}
+
+
+	/**
+	 *  update - update rows in the table used to store the choices 
+	 *  for a selection box. This function is used only for extra  
+	 *  boxes and fields configured by the admin
+	 *
+	 *  @param	string	Name of the choice in a box.
+	 *  @param	int	Id of the box 
+	 *  @param	int	id of the row
+	 *  @return	boolean	success.
+	 */
+	function update($name,$boxid,$id) {
+		if (!$this->ArtifactType->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		if (!$name) {
+			$this->setMissingParamsError();
+			return false;
+		}   
+		$sql="UPDATE artifact_group_selection_box_options 
+			SET box_options_name='".htmlspecialchars($name)."' 
+			WHERE id='$id'"; 
+//			AND artifact_box_id='$boxid'";
+		$result=db_query($sql);
+		if ($result && db_affected_rows($result) > 0) {
+			return true;
+		} else {
+			$this->setError(db_error());
+			return false;
+		}
+	}
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/tracker/ArtifactCanned.class
===================================================================
--- trunk/gforge_base/gforge/common/tracker/ArtifactCanned.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/tracker/ArtifactCanned.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,216 @@
+<?php
+/**
+ * ArtifactCanned.class - Class to handle canned responses
+ *
+ * Copyright 1999-2001 (c) VA Linux Systems
+ * The rest Copyright 2002-2004 (c) GForge Team
+ * http://gforge.org/
+ *
+ * @version   $Id: ArtifactCanned.class 5527 2006-06-05 20:10:10Z lo-lan-do $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+require_once('common/include/Error.class');
+
+class ArtifactCanned extends Error {
+
+	/** 
+	 * The artifact type object.
+	 *
+	 * @var		object	$ArtifactType.
+	 */
+	var $ArtifactType; 
+
+	/**
+	 * Array of artifact data.
+	 *
+	 * @var		array	$data_array.
+	 */
+	var $data_array;
+
+	/**
+	 *  ArtifactCanned - constructor.
+	 *
+	 *	@param	object	The Artifact Type object.
+	 *  @param	array	(all fields from artifact_file_user_vw) OR id from database.
+	 *  @return	boolean	success.
+	 */
+	function ArtifactCanned(&$ArtifactType, $data=false) {
+		$this->Error(); 
+
+		//was ArtifactType legit?
+		if (!$ArtifactType || !is_object($ArtifactType)) {
+			$this->setError('ArtifactCanned: No Valid ArtifactType');
+			return false;
+		}
+		//did ArtifactType have an error?
+		if ($ArtifactType->isError()) {
+			$this->setError('ArtifactCanned: '.$Artifact->getErrorMessage());
+			return false;
+		}
+		$this->ArtifactType =& $ArtifactType;
+
+		if ($data) {
+			if (is_array($data)) {
+				$this->data_array =& $data;
+				return true;
+			} else {
+				if (!$this->fetchData($data)) {
+					return false;
+				} else {
+					return true;
+				}
+			}
+		}
+	}
+
+	/**
+	 *	create - create a new item in the database.
+	 *
+	 *	@param	string	The item title.
+	 *	@param	string	The item body.
+	 *  @return id on success / false on failure.
+	 */
+	function create($title, $body) {
+		global $Language;
+		//
+		//	data validation
+		//
+		if (!$title || !$body) {
+			$this->setError($Language->getText('artifact_canned','required_fields'));
+			return false;
+		}
+		if (!$this->ArtifactType->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		$sql="INSERT INTO artifact_canned_responses (group_artifact_id,title,body) 
+			VALUES ('".$this->ArtifactType->getID()."',
+			'". htmlspecialchars($title) ."','". htmlspecialchars($body) ."')";
+
+		$result=db_query($sql);
+
+		if ($result && db_affected_rows($result) > 0) {
+			$this->clearError();
+			return true;
+		} else {
+			$this->setError(db_error());
+			return false;
+		}
+
+/*
+			//
+			//	Now set up our internal data structures
+			//
+			if (!$this->fetchData($id)) {
+				return false;
+			}
+*/
+	}
+
+	/**
+	 *	fetchData - re-fetch the data for this ArtifactCanned from the database.
+	 *
+	 *	@param int	The ID number.
+	 *	@return	boolean	success.
+	 */
+	function fetchData($id) {
+		$res=db_query("SELECT * FROM artifact_canned_responses WHERE id='$id'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('ArtifactCanned: Invalid ArtifactCanned ID');
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	getArtifactType - get the ArtifactType Object this ArtifactCanned message is associated with.
+	 *
+	 *	@return ArtifactType.
+	 */
+	function &getArtifactType() {
+		return $this->ArtifactType;
+	}
+	
+	/**
+	 *	getID - get this ArtifactCanned message's ID.
+	 *
+	 *	@return	int	The id #.
+	 */
+	function getID() {
+		return $this->data_array['id'];
+	}
+
+	/**
+	 *	getTitle - get the title.
+	 *
+	 *	@return	string	The title.
+	 */
+	function getTitle() {
+		return $this->data_array['title'];
+	}
+
+	/**
+	 *	getBody - get the body of this message.
+	 *
+	 *	@return	string	The message body.
+	 */
+	function getBody() {
+		return $this->data_array['body'];
+	}
+
+	/**
+	 *  update - update an ArtifactCanned message.
+	 *
+	 *  @param	string	Title of the message.
+	 *  @param	string	Body of the message.
+	 *  @return	boolean	success.
+	 */
+	function update($title,$body) {
+		if (!$this->ArtifactType->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}   
+		if (!$title || !$body) {
+			$this->setMissingParamsError();
+			return false;
+		}   
+
+		$sql="UPDATE artifact_canned_responses 
+			SET title='". htmlspecialchars($title) ."',body='". htmlspecialchars($body) ."'
+			WHERE group_artifact_id='". $this->ArtifactType->getID() ."' AND id='". $this->getID() ."'";
+
+		$result=db_query($sql);
+
+		if ($result && db_affected_rows($result) > 0) {
+			return true;
+		} else {
+			$this->setError(db_error());
+			return false;
+		}
+	}
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/tracker/ArtifactExtraField.class
===================================================================
--- trunk/gforge_base/gforge/common/tracker/ArtifactExtraField.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/tracker/ArtifactExtraField.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,525 @@
+<?php
+/**
+ * ArtifactExtraField.class - Class to handle user defined artifacts
+ *
+ * Copyright 2004 (c) Anthony J. Pugliese
+ *
+ * @version   $Id: ArtifactExtraField.class 5527 2006-06-05 20:10:10Z lo-lan-do $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+require_once('common/include/Error.class');
+
+define('ARTIFACT_EXTRAFIELD_FILTER_INT','1,2,3,5,7');
+define('ARTIFACT_EXTRAFIELDTYPE_SELECT',1);
+define('ARTIFACT_EXTRAFIELDTYPE_CHECKBOX',2);
+define('ARTIFACT_EXTRAFIELDTYPE_RADIO',3);
+define('ARTIFACT_EXTRAFIELDTYPE_TEXT',4);
+define('ARTIFACT_EXTRAFIELDTYPE_MULTISELECT',5);
+define('ARTIFACT_EXTRAFIELDTYPE_TEXTAREA',6);
+define('ARTIFACT_EXTRAFIELDTYPE_STATUS',7);
+//define('ARTIFACT_EXTRAFIELDTYPE_ASSIGNEE',8);
+
+class ArtifactExtraField extends Error {
+
+	/** 
+	 * The artifact type object.
+	 *
+	 * @var		object	$ArtifactType.
+	 */
+	var $ArtifactType; //object
+
+	/**
+	 * Array of artifact data.
+	 *
+	 * @var		array	$data_array.
+	 */
+	var $data_array;
+
+	/**
+	 *	ArtifactExtraField - Constructer
+	 *
+	 *	@param	object	ArtifactType object.
+	 *  @param	array	(all fields from artifact_file_user_vw) OR id from database.
+	 *  @return	boolean	success.
+	 */
+	function ArtifactExtraField(&$ArtifactType, $data=false) {
+		$this->Error(); 
+
+		//was ArtifactType legit?
+		if (!$ArtifactType || !is_object($ArtifactType)) {
+			$this->setError('ArtifactExtraField: No Valid ArtifactType');
+			return false;
+		}
+		//did ArtifactType have an error?
+		if ($ArtifactType->isError()) {
+			$this->setError('ArtifactExtraField: '.$Artifact->getErrorMessage());
+			return false;
+		}
+		$this->ArtifactType =& $ArtifactType;
+
+		if ($data) {
+			if (is_array($data)) {
+				$this->data_array =& $data;
+				return true;
+			} else {
+				if (!$this->fetchData($data)) {
+					return false;
+				} else {
+					return true;
+				}
+			}
+		}
+	}
+
+	/**
+	 *	create - create a row in the table that stores box names for a
+	 *	a tracker.  This function is only used to create rows for boxes 
+	 *	configured by the admin.
+	 *
+	 *	@param	string	Name of the extra field.
+	 *	@param	int	The type of field - radio, select, text, textarea
+	 *	@param	int	Attribute1 - for text (size) and textarea (rows)
+	 *	@param	int	Attribute2 - for text (maxlength) and textarea (cols)
+	 *	@param	int	is_required - true or false whether this is a required field or not.
+	 *	@param	string	alias - alias for this extra field (optional)
+	 *  @return 	true on success / false on failure.
+	 */
+	function create($name,$field_type,$attribute1,$attribute2,$is_required=0,$alias='') {
+		global $Language;
+
+		//
+		//	data validation
+		//
+		if (!$name) {
+			$this->setError($Language->getText('tracker_admin_build_boxes','required_box_name'));
+			return false;
+		}
+		if (!$this->ArtifactType->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		if ($is_required) {
+			$is_required=1;
+		} else {
+			$is_required=0;
+		}	
+		
+		if (!($alias = $this->generateAlias($alias,$name))) {
+			return false;
+		}
+		
+		$sql="INSERT INTO artifact_extra_field_list (group_artifact_id,field_name,
+			field_type,attribute1,attribute2,is_required,alias) 
+			VALUES ('".$this->ArtifactType->getID()."','".htmlspecialchars($name)."',
+			'$field_type','$attribute1','$attribute2','$is_required','$alias')";
+
+		db_begin();
+		$result=db_query($sql);
+
+		if ($result && db_affected_rows($result) > 0) {
+			$this->clearError();
+			$id=db_insertid($result,'artifact_extra_field_list','extra_field_id');
+			//
+			//	Now set up our internal data structures
+			//
+			if (!$this->fetchData($id)) {
+				db_rollback();
+				return false;
+			}
+			if ($field_type == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
+				if (!$this->ArtifactType->setCustomStatusField($id)) {
+					db_rollback();
+					return false;
+				} else {
+//
+//	Must insert some default statuses for each artifact
+//
+					$reso=db_query("INSERT INTO artifact_extra_field_elements(extra_field_id,element_name,status_id) 
+						values ('$id','Open','1')");
+					if (!$reso) {
+						echo db_error();
+					} else {
+						$resoid=db_insertid($reso,'artifact_extra_field_elements','element_id');
+						db_query("INSERT INTO artifact_extra_field_data(artifact_id,field_data,extra_field_id) 
+							SELECT artifact_id,$resoid,$id FROM artifact 
+							WHERE group_artifact_id='".$this->ArtifactType->getID()."'
+							AND status_id=1");
+					}
+					$resc=db_query("INSERT INTO artifact_extra_field_elements(extra_field_id,element_name,status_id)
+						values ('$id','Closed','2')");
+					if (!$resc) {
+						echo db_error();
+					} else {
+						$rescid=db_insertid($resc,'artifact_extra_field_elements','element_id');
+						db_query("INSERT INTO artifact_extra_field_data(artifact_id,field_data,extra_field_id) 
+							SELECT artifact_id,$rescid,$id FROM artifact 
+							WHERE group_artifact_id='".$this->ArtifactType->getID()."'
+							AND status_id != 1");
+					}
+				}
+			} elseif (strstr(ARTIFACT_EXTRAFIELD_FILTER_INT,$field_type) !== false) {
+//
+//	Must insert some default 100 rows for the data table so None queries will work right
+//
+				$resdefault=db_query("INSERT INTO artifact_extra_field_data(artifact_id,field_data,extra_field_id) 
+					SELECT artifact_id,100,$id FROM artifact WHERE group_artifact_id='".$this->ArtifactType->getID()."'");
+				if (!$resdefault) {
+					echo db_error();
+				}
+			}
+			db_commit();
+			return $id;
+		} else {
+			$this->setError(db_error());
+			db_rollback();
+			return false;
+		}
+	}
+
+	/**
+	 *	fetchData - re-fetch the data for this ArtifactExtraField from the database.
+	 *
+	 *	@param	int		ID of the Box.
+	 *	@return	boolean	success.
+	 */
+	function fetchData($id) {
+		$this->id=$id;
+		$res=db_query("SELECT * FROM artifact_extra_field_list WHERE extra_field_id='$id'");
+		
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('ArtifactExtraField: Invalid ArtifactExtraField ID');
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	getArtifactType - get the ArtifactType Object this ArtifactExtraField is associated with.
+	 *
+	 *	@return object	ArtifactType.
+	 */
+	function &getArtifactType() {
+		return $this->ArtifactType;
+	}
+	
+	/**
+	 *	getID - get this ArtifactExtraField ID.
+	 *
+	 *	@return	int	The id #.
+	 */
+	function getID() {
+		return $this->data_array['extra_field_id'];
+	}
+
+	/**
+	 *	getName - get the name.
+	 *
+	 *	@return	string	The name.
+	 */
+	function getName() {
+		return $this->data_array['field_name'];
+	}
+
+	/**
+	 *	getAttribute1 - get the attribute1 field.
+	 *
+	 *	@return int	The first attribute.
+	 */
+	function getAttribute1() {
+		return $this->data_array['attribute1'];
+	}
+
+	/**
+	 *	getAttribute2 - get the attribute2 field.
+	 *
+	 *	@return int	The second attribute.
+	 */
+	function getAttribute2() {
+		return $this->data_array['attribute2'];
+	}
+
+	/**
+	 *	getType - the type of field.
+	 *
+	 *	@return	int	type.
+	 */
+	function getType() {
+		return $this->data_array['field_type'];
+	}
+
+	/**
+	 *	getTypeName - the name of type of field.
+	 *
+	 *	@return	string	type.
+	 */
+	function getTypeName() {
+		$arr=&$this->getAvailableTypes();
+		return $arr[$this->data_array['field_type']];
+	}
+
+	/**
+	 *	isRequired - whether this field is required or not.
+	 *
+	 *	@return	boolean required.
+	 */
+	function isRequired() {
+		return $this->data_array['is_required'];
+	}
+
+	/**
+	 *	getAvailableTypes - the types of text fields and their names available.
+	 *
+	 *	@return	array	types.
+	 */
+	function getAvailableTypes() {
+		global $Language;
+		return array(
+			1=>$Language->getText('tracker_admin_build_boxes','box_type_select'),
+			2=>$Language->getText('tracker_admin_build_boxes','box_type_checkbox'),
+			3=>$Language->getText('tracker_admin_build_boxes','box_type_radio'),
+			4=>$Language->getText('tracker_admin_build_boxes','box_type_text'),
+			5=>$Language->getText('tracker_admin_build_boxes','box_type_multiselect'),
+			6=>$Language->getText('tracker_admin_build_boxes','box_type_textarea'),
+			7=>$Language->getText('tracker_admin_build_boxes','box_type_status')
+		);
+	}
+	
+	/**
+	 *	getAlias - the alias that is used for this field
+	 *
+	 *	@return	string	alias
+	 */
+	function getAlias() {
+		return $this->data_array['alias'];
+	}
+	
+	/**
+	 *	getAvailableValues - Get the list of available values for this extra field
+	 *
+	 *	@return array
+	 */
+	function getAvailableValues() {
+		$sql = "SELECT * FROM artifact_extra_field_elements WHERE extra_field_id=".$this->getID();
+		$res = db_query($sql);
+		
+		$return = array();
+		while ($row = db_fetch_array($res)) {
+			$return[] = $row;
+		}
+		return $return;
+	}
+
+	/**
+	 *  update - update a row in the table used to store box names 
+	 *  for a tracker.  This function is only to update rowsf
+	 *  for boxes configured by
+	 *  the admin.
+	 *
+	 *  @param	string	Name of the field.
+	 *	@param	int	Attribute1 - for text (size) and textarea (rows)
+	 *	@param	int	Attribute2 - for text (maxlength) and textarea (cols)
+	 *	@param	int	is_required - true or false whether this is a required field or not.
+	 *	@param	string	Alias for this field
+	 *  @return	boolean	success.
+	 */
+	function update($name,$attribute1,$attribute2,$is_required=0,$alias="") {
+		global $Language;
+
+		if (!$this->ArtifactType->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		//
+		//	data validation
+		//
+		if (!$name) {
+			$this->setError($Language->getText('tracker_admin_build_boxes','required_box_name'));
+			return false;
+		}
+		if ($is_required) {
+			$is_required=1;
+		} else {
+			$is_required=0;
+		}
+		
+		if (!($alias = $this->generateAlias($alias,$name))) {
+			return false;
+		}		
+
+		$sql="UPDATE artifact_extra_field_list 
+			SET 
+			field_name='".htmlspecialchars($name)."',
+			attribute1='$attribute1',
+			attribute2='$attribute2',
+			is_required='$is_required',
+			alias='$alias'
+			WHERE extra_field_id='". $this->getID() ."' 
+			AND group_artifact_id='".$this->ArtifactType->getID()."'";
+		$result=db_query($sql);
+		if ($result && db_affected_rows($result) > 0) {
+			return true;
+		} else {
+			$this->setError(db_error());
+			return false;
+		}
+	}
+
+	/**
+	 *
+	 *
+	 */
+	function delete($sure, $really_sure) {
+		if (!$sure || !$really_sure) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		if (!$this->ArtifactType->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		db_begin();
+		$sql="DELETE FROM artifact_extra_field_data 
+			WHERE extra_field_id='".$this->getID()."'";
+		$result=db_query($sql);
+		if ($result) {
+			$sql="DELETE FROM artifact_extra_field_elements
+				WHERE extra_field_id='".$this->getID()."'";
+			$result=db_query($sql);
+			if ($result) {
+				$sql="DELETE FROM artifact_extra_field_list
+                WHERE extra_field_id='".$this->getID()."'";
+				$result=db_query($sql);
+				if ($result) {
+					if ($this->getType() == ARTIFACT_EXTRAFIELDTYPE_STATUS) {
+						if (!$this->ArtifactType->setCustomStatusField(0)) {
+							db_rollback();
+							return false;
+						}
+					}
+					db_commit();
+					return true;
+				} else {
+					$this->setError(db_error());
+					db_rollback();
+					return false;
+				}
+			} else {
+				$this->setError(db_error());
+				db_rollback();
+				return false;
+			}
+		} else {
+			$this->setError(db_error());
+			db_rollback();
+			return false;
+		}
+
+	}
+	
+	/**
+	 * 	Validate an alias.
+	 *	Note that this function does not check for conflicts.
+	 *	@param	string	alias - alias to validate
+	 *	@return	bool	true if alias is valid, false otherwise and it sets the corresponding error
+	 */
+	function validateAlias($alias) {
+		global $Language;
+
+		// these are reserved alias names
+		static $reserved_alias = array(
+			"project",
+			"type",
+			"priority",
+			"assigned_to",
+			"summary",
+			"details"
+		);
+
+		if (strlen($alias) == 0) return true;		// empty alias
+
+		// invalid chars?
+		if (preg_match("/[^[:alnum:]_\\-]/", $alias)) {
+			$this->setError($Language->getText('tracker_admin_build_boxes','invalid_alias'));
+			return false;
+		} else if (in_array($alias, $reserved_alias)) {	// alias is reserved?
+			$this->setError($Language->getText('tracker_admin_build_boxes','reserved_alias', array($alias)));
+			return false;
+		}
+		
+		return true;
+	}
+	
+	/**
+	 *	Generate an alias for this field. The alias can be entered by the user or
+	 *	be generated automatically from the name of the field.
+	 *	@param	string	Alias entered by the user
+	 *	@param	string	Name of the field entered by the user (it'll be used when $alias is empty)
+	 *	@return	string
+	 */
+	function generateAlias($alias, $name) {
+		$alias = strtolower(trim($alias));
+		if (strlen($alias) == 0) {		// no alias was entered, generate alias from $name
+			$name = strtolower(trim($name));
+			// Convert the original name to a valid alias (i.e., if the extra field is 
+			// called "Quality test", make an alias called "quality_test").
+			// The alias can be seen as a "unix name" for this field
+			$alias = preg_replace("/ /", "_", $name);
+			$alias = preg_replace("/[^[:alnum:]_]/", "", $alias);
+			$alias = strtolower($alias);
+		} elseif (!$this->validateAlias($alias)) {
+			// alias is invalid...
+			return false;
+		} 
+		// check if the name conflicts with another alias in the same artifact type
+		// in that case append a serial number to the alias
+		$serial = 1;
+		$conflict = false;	
+		do {
+			$sql = "SELECT * FROM artifact_extra_field_list ".
+					"WHERE LOWER(alias)='".$alias."' AND ".
+					"group_artifact_id=".$this->ArtifactType->getID();
+			if ($this->data_array['extra_field_id']) {
+				$sql .= " AND extra_field_id <> ".$this->data_array['extra_field_id'];
+			}
+			$res = db_query($sql);
+
+			if (!$res) {
+				$this->setError(db_error());
+				return false;
+			} else if (db_numrows($res) > 0) {		// found another field with the same alias
+				$conflict = true;
+				$serial++;
+				$alias = $alias.$serial;
+			} else {
+				$conflict = false;
+			}
+		} while ($conflict);
+
+		// at this point, the alias is valid and unique
+		return $alias;
+	}
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/tracker/ArtifactExtraFieldElement.class
===================================================================
--- trunk/gforge_base/gforge/common/tracker/ArtifactExtraFieldElement.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/tracker/ArtifactExtraFieldElement.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,242 @@
+<?php
+/**
+ * ArtifactExtraField.class - Class to handle user defined artifacts
+ *
+ * Copyright 2004 (c) Anthony J. Pugliese
+ *
+ * @version   $Id: ArtifactExtraFieldElement.class 5527 2006-06-05 20:10:10Z lo-lan-do $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+require_once('common/include/Error.class');
+
+class ArtifactExtraFieldElement extends Error {
+
+	/** 
+	 * The artifact type object.
+	 *
+	 * @var		object	$ArtifactExtraField.
+	 */
+	var $ArtifactExtraField; //object
+
+	/**
+	 * Array of artifact data.
+	 *
+	 * @var		array	$data_array.
+	 */
+	var $data_array;
+	/**
+	 *	ArtifactExtraFieldElement - Constructer
+	 *
+	 *	@param	object	ArtifactExtraField object.
+	 *  @param	array	(all fields from artifact_file_user_vw) OR id from database.
+	 *  @return	boolean	success.
+	 */
+	function ArtifactExtraFieldElement(&$ArtifactExtraField,$data=false) {
+		$this->Error(); 
+		
+		//was ArtifactExtraField legit?
+		if (!$ArtifactExtraField || !is_object($ArtifactExtraField)) {
+			$this->setError('ArtifactExtraField: No Valid ArtifactExtraField');
+			return false;
+		}
+		//did ArtifactExtraField have an error?
+		if ($ArtifactExtraField->isError()) {
+			$this->setError('ArtifactExtraField: '.$ArtifactExtraField->getErrorMessage());
+			return false;
+		}
+		$this->ArtifactExtraField =& $ArtifactExtraField;
+		if ($data) {
+			if (is_array($data)) {
+//TODO validate that data actually belongs in this ArtifactExtraField
+				$this->data_array =& $data;
+				return true;
+			} else {
+				if (!$this->fetchData($data)) {
+					return false;
+				} else {
+					return true;
+				}
+			}
+		}
+	}
+
+	/**
+	 *	create - create a new row in the table used to store the 
+	 *	choices for selection boxes.  This function is only used for 
+	 *	extra fields and boxes configured by the admin
+	 *
+	 *	@param	string		Name of the choice
+	 *	@param	int		Id the box that contains the choice.
+	 *  @param  int status_id - optional for status box - maps to either open/closed.
+	 *  @return 	true on success / false on failure.
+	 */
+	
+	function create($name,$status_id=0) {
+		global $Language;
+		//
+		//	data validation
+		//
+		if (!$name) {
+			$this->setError($Language->getText('tracker_admin_build_boxes','required_choice_name'));
+			return false;
+		}
+		if ($status_id) {
+			if ($status_id==1) {
+			} else {
+				$status_id=2;
+			}
+		} else {
+			$status_id=0;
+		}
+		if (!$this->ArtifactExtraField->ArtifactType->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		$sql="INSERT INTO artifact_extra_field_elements (extra_field_id,element_name,status_id) 
+			VALUES ('".$this->ArtifactExtraField->getID()."','".htmlspecialchars($name)."','$status_id')";
+		db_begin();
+		$result=db_query($sql);
+		if ($result && db_affected_rows($result) > 0) {
+			$this->clearError();
+			$id=db_insertid($result,'artifact_extra_field_elements','element_id');
+			//
+			//	Now set up our internal data structures
+			//
+			if (!$this->fetchData($id)) {
+				db_rollback();
+				return false;
+			} else {
+				db_commit();
+				return $id;
+			}
+		} else {
+			$this->setError(db_error());
+			db_rollback();
+			return false;
+		}
+	}
+
+
+	/**
+	 *	fetchData - re-fetch the data for this ArtifactExtraFieldElement from the database.
+	 *
+	 *	@param	int		ID of the Box.
+	 *	@return	boolean	success.
+	 */
+	function fetchData($id) {
+		$res=db_query("SELECT * FROM artifact_extra_field_elements WHERE element_id='$id'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('ArtifactExtraField: Invalid ArtifactExtraFieldElement ID');
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	getArtifactExtraField - get the ArtifactExtraField Object this ArtifactExtraField is associated with.
+	 *
+	 *	@return object	ArtifactExtraField.
+	 */
+	function &getArtifactExtraField() {
+		return $this->ArtifactExtraField;
+	}
+	
+	/**
+	 *	getID - get this ArtifactExtraField ID.
+	 *
+	 *	@return	int	The id #.
+	 */
+	function getID() {
+		return $this->data_array['element_id'];
+	}
+	
+	/**
+	 *	getBoxID - get this  artifact box id.
+	 *
+	 *	@return	int	The id #.
+	 */
+	function getBoxID() {
+		return $this->data_array['extra_field_id'];
+	}
+
+	/**
+	 *	getName - get the name.
+	 *
+	 *	@return	string	The name.
+	 */
+	function getName() {
+		return $this->data_array['element_name'];
+	}
+
+	/**
+	 *  getStatus - the status equivalent of this field (open or closed).
+	 *
+	 *  @return int status.
+	 */
+	function getStatusID() {
+		return $this->data_array['status_id'];
+	}
+
+	/**
+	 *  update - update rows in the table used to store the choices 
+	 *  for a selection box. This function is used only for extra  
+	 *  boxes and fields configured by the admin
+	 *
+	 *  @param	string	Name of the choice in a box.
+	 *  @param  int status_id - optional for status box - maps to either open/closed.
+	 *  @return	boolean	success.
+	 */
+	function update($name,$status_id=0) {
+		if (!$this->ArtifactExtraField->ArtifactType->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		if (!$name) {
+			$this->setMissingParamsError();
+			return false;
+		}   
+		if ($status_id) {
+			if ($status_id==1) {
+			} else {
+				$status_id=2;
+			}
+		} else {
+			$status_id=0;
+		}
+		$sql="UPDATE artifact_extra_field_elements 
+			SET element_name='".htmlspecialchars($name)."',
+			status_id='$status_id' 
+			WHERE element_id='".$this->getID()."'"; 
+		$result=db_query($sql);
+		if ($result && db_affected_rows($result) > 0) {
+			return true;
+		} else {
+			$this->setError(db_error());
+			return false;
+		}
+	}
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/tracker/ArtifactFactory.class
===================================================================
--- trunk/gforge_base/gforge/common/tracker/ArtifactFactory.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/tracker/ArtifactFactory.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,373 @@
+<?php
+/**
+ * GForge Tracker Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: ArtifactFactory.class 5527 2006-06-05 20:10:10Z lo-lan-do $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+
+require_once('common/include/Error.class');
+require_once('common/tracker/Artifact.class');
+require_once('common/tracker/ArtifactType.class');
+require_once('common/tracker/ArtifactQuery.class');
+
+class ArtifactFactory extends Error {
+
+	/**
+	 * The ArtifactType object.
+	 *
+	 * @var	 object  $ArtifactType.
+	 */
+	var $ArtifactType;
+
+	/**
+	 * The artifacts array.
+	 *
+	 * @var  array  artifacts.
+	 */
+	var $artifacts = array();
+	var $order_col;
+	var $sort;
+	var $status;
+	var $changed_from;
+	var $last_changed;
+	var $assigned_to;
+	var $offset;
+	var $max_rows;
+	var $fetched_rows;
+	var $extra_fields;
+	var $defaultquery;
+	var $moddaterange;
+	var $opendaterange;
+	var $closedaterange;
+
+	/**
+	 *  Constructor.
+	 *
+	 *	@param	object	The ArtifactType object to which this ArtifactFactory is associated.
+	 *	@return	boolean	success.
+	 */
+	function ArtifactFactory(&$ArtifactType) {
+		$this->Error();
+		if (!$ArtifactType || !is_object($ArtifactType)) {
+			$this->setError('ArtifactFactory:: No Valid ArtifactType Object');
+			return false;
+		}
+		if ($ArtifactType->isError()) {
+			$this->setError('ArtifactFactory:: '.$ArtifactType->getErrorMessage());
+			return false;
+		}
+		$this->ArtifactType =& $ArtifactType;
+		$this->changed_from = 0x7ffffff; // Any
+
+		return true;
+	}
+
+	/**
+	 *	setup - sets up limits and sorts before you call getTasks().
+	 *
+	 *	@param	int	The offset - number of rows to skip.
+	 *	@param	string	The column to sort on.
+	 *	@param	string	The way to order - ASC or DESC.
+	 *	@param	int	The max number of rows to return.
+	 *	@param	string	Whether to set these prefs into the user_prefs table - use "custom".
+	 *	@param	int	Include this param if you want to limit to a certain assignee.
+	 *	@param	int	Include this param if you want to limit to a particular status.
+	 *	@param	array	Array of extra fields & elements to limit the query to.
+	 */
+	function setup($offset,$order_col,$sort,$max_rows,$set,$_assigned_to,$_status,$_extra_fields=array()) {
+//echo "<BR>offset: $offset| order: $order|max_rows: $max_rows|_assigned_to: $_assigned_to|_status: $_status";
+
+		if ((!$offset) || ($offset < 0)) {
+			$this->offset=0;
+		} else {
+			$this->offset=$offset;
+		}
+
+		if (session_loggedin()) {
+			$u =& session_get_user();
+		}
+		if (!is_array($_extra_fields)) {
+			$_extra_fields=array();
+		}
+
+		if (!$set) {
+			/*
+				if no set is passed in, see if a preference was set
+				if no preference or not logged in, use open set
+			*/
+			if (session_loggedin()) {
+				$default_query=$u->getPreference('art_query'.$this->ArtifactType->getID());
+				$this->defaultquery = $default_query;
+				if ($default_query) {
+					$aq = new ArtifactQuery($this->ArtifactType,$default_query);
+					$_extra_fields=$aq->getExtraFields();
+					$order_col=$aq->getSortCol();
+					$sort=$aq->getSortOrd();
+					$_assigned_to=$aq->getAssignee();
+					$_status=$aq->getStatus();
+					$this->moddaterange = $aq->getModDateRange();
+					$this->opendaterange = $aq->getOpenDateRange();
+					$this->closedaterange = $aq->getCloseDateRange();
+				} else {
+					$custom_pref=$u->getPreference('art_cust'.$this->ArtifactType->getID());
+					if ($custom_pref) {
+//$_assigned_to.'|'.$_status.'|'.$_order_col.'|'.$_sort_ord.'|'.$_changed.'|'.serialize($_extra_fields);
+						$pref_arr=explode('|',$custom_pref);
+						$_assigned_to=$pref_arr[0];
+						$_status=$pref_arr[1];
+						$order_col=$pref_arr[2];
+						$sort=$pref_arr[3];
+						$_changed=$pref_arr[4];
+						if ($this->ArtifactType->usesCustomStatuses()) {
+							$_extra_fields=unserialize($pref_arr[5]);
+						} else {
+							$_status=$pref_arr[1];
+						}
+						$set='custom';
+					} else {
+						//default to open
+						$_assigned_to=0;
+						$_status=1;
+						$_changed=0;
+					}
+				}
+			} else {
+				//default to open
+				$_assigned_to=0;
+				$_status=1;
+			}
+		}
+
+		//
+		//  validate the column names and sort order passed in from user
+		//  before saving it to prefs
+		//
+		if ($order_col=='artifact_id' || $order_col=='summary' || $order_col=='open_date' ||
+			$order_col=='close_date' || $order_col=='assigned_to' || $order_col=='submitted_by' || $order_col=='priority') {
+			$_order_col=$order_col;
+			if (($sort == 'ASC') || ($sort == 'DESC')) {
+				$_sort_ord=$sort;
+			} else {
+				$_sort_ord='ASC';
+			}
+		} else {
+			$_order_col='artifact_id';
+			$_sort_ord='ASC';
+		}
+
+		if ($set=='custom') {
+			if (session_loggedin()) {
+				/*
+					if this custom set is different than the stored one, reset preference
+				*/
+				if (is_array($_assigned_to)) {
+					$_assigned_to='';
+				}
+				$aux_extra_fields = array();
+				if (is_array($_extra_fields)){
+					//print_r($_extra_fields);
+					$keys=array_keys($_extra_fields);
+					
+					foreach ($keys as $key) {
+						if ($_extra_fields[$key] != 'Array') {
+							$aux_extra_fields[$key] = $_extra_fields[$key];
+						}
+					}
+				}
+
+				$extra_pref = '';
+				if (count($aux_extra_fields)>0) {
+					$extra_pref = '|'.serialize($aux_extra_fields);
+				}
+				
+				$pref_=$_assigned_to.'|'.$_status.'|'.$_order_col.'|'.$_sort_ord.'|'.$_changed.$extra_pref;
+				if ($pref_ != $u->getPreference('art_cust'.$this->ArtifactType->getID())) {
+					$u->setPreference('art_cust'.$this->ArtifactType->getID(),$pref_);
+				}
+				$default_query=$u->getPreference('art_query'.$this->ArtifactType->getID());
+				if ($default_query) {
+					$u->deletePreference('art_query'.$this->ArtifactType->getID());
+				}
+			}
+		}
+
+		$this->sort=$_sort_ord;
+		$this->order_col=$_order_col;
+		$this->status=$_status;
+		if ($_assigned_to != 'Array') {
+			$this->assigned_to=$_assigned_to;
+		}
+		$this->extra_fields=$_extra_fields;
+		$this->setChangedFrom($_changed);
+
+		// if $max_rows == 0 it means we want all the rows
+		if (is_null($max_rows) || $max_rows < 0) {
+			$max_rows=50;
+		}
+		if ($default_query) {
+			$this->max_rows=0;
+		} else {
+			$this->max_rows=$max_rows;
+		}
+	}
+
+	
+	/**
+	 *	setChangedFrom - sets up changed-from and last-changed before you call getTasks().
+	 *
+	 *	@param	int	The changed_from - offset time(sec) from now
+	 */
+	function setChangedFrom($changed_from) {
+		$this->changed_from = ($changed_from <= 0) ? 0x7fffffff : $changed_from;
+		$this->last_changed = time() - $this->changed_from;
+	}
+
+	/**
+	 *	getDefaultQuery - get the default query
+	 *
+	 *	@return	int	
+	 */
+	function getDefaultQuery() {
+		return $this->defaultquery;
+	}
+	
+	/**
+	 *	getArtifacts - get an array of Artifact objects.
+	 *
+	 *	@return	array	The array of Artifact objects.
+	 */
+	function &getArtifacts() {
+		if (!empty($this->artifacts)) {
+			return $this->artifacts;
+		}
+
+		//if status selected, and more to where clause
+		if ($this->status && ($this->status != 100)) {
+			//for open tasks, add status=100 to make sure we show all
+			$status_str="AND status_id='".$this->status."'";
+		} else {
+			//no status was chosen, so don't add it to where clause
+			$status_str='';
+		}
+
+		//if assigned to selected, and more to where clause
+		if ($this->assigned_to) {
+			if (is_array($this->assigned_to)) {
+				$assigned_str="AND assigned_to IN (".implode(',',$this->assigned_to).")";
+			} else {
+				$assigned_str="AND assigned_to='".$this->assigned_to."'";
+			}
+		} else {
+			//no assigned to was chosen, so don't add it to where clause
+			$assigned_str='';
+		}
+
+		if (is_array($this->extra_fields) && !empty($this->extra_fields)) {
+			$keys=array_keys($this->extra_fields);
+			$vals=array_values($this->extra_fields);
+			$ef_where_str='';
+			$ef_table_str='';
+			for ($i=0; $i<count($keys); $i++) {
+				if (empty($vals[$i])) {
+					continue;
+				}
+				if (is_array($vals[$i]) && !empty($vals[$i])) {
+					$vals[$i]=implode(',',$vals[$i]);
+				}
+				$ef_table_str.=", artifact_extra_field_data aefd$i ";
+				$ef_where_str.=" AND aefd$i.extra_field_id='".$keys[$i]."' AND aefd$i.field_data IN (".$vals[$i].") AND aefd$i.artifact_id=artifact_vw.artifact_id ";
+			}
+		} else {
+			$ef_table_str='';
+			$ef_where_str='';
+		}
+
+		if ($this->last_changed > 0) {
+			$last_changed_str=" AND last_modified_date > '" . $this->last_changed . "' ";
+		} else {
+			$last_changed_str='';
+		}
+
+		//add constraint of range of modified dates
+		if ($this->moddaterange) {
+			$range_arr=explode(' ',$this->moddaterange);
+			$begin_int = strtotime($range_arr[0]);
+			$end_int=strtotime($range_arr[1])+(24*60*60);
+			$moddatesql= " AND last_modified_date BETWEEN '$begin_int' AND '$end_int' ";
+		} else {
+			$moddatesql= '';
+		}
+		//add constraint of range of open dates
+		if ($this->opendaterange) {
+			$range_arr=explode(' ',$this->opendaterange);
+			$begin_int = strtotime($range_arr[0]);
+			$end_int=strtotime($range_arr[1])+(24*60*60);
+			$opendatesql= " AND open_date BETWEEN '$begin_int' AND '$end_int' ";
+		} else {
+			$opendatesql= '';
+		}
+		//add constraint of range of close dates
+		if ($this->closedaterange) {
+			$range_arr=explode(' ',$this->closedaterange);
+			$begin_int = strtotime($range_arr[0]);
+			$end_int=strtotime($range_arr[1])+(24*60*60);
+			$closedatesql= " AND close_date BETWEEN '$begin_int' AND '$end_int' ";
+		} else {
+			$closedatesql= '';
+		}
+		
+		// these are currently not being used
+		$submitted_by_str = '';
+		
+		//
+		//  now run the query using the criteria chosen above
+		//
+		$sql="SELECT * FROM (SELECT DISTINCT ON (group_artifact_id, artifact_id) artifact_vw.* FROM artifact_vw $ef_table_str
+			WHERE 
+			group_artifact_id='". $this->ArtifactType->getID() ."'
+			$opendatesql $moddatesql $closedatesql 
+			 $status_str $assigned_str $last_changed_str $ef_where_str ) AS Artifacts
+			ORDER BY Artifacts.group_artifact_id ".$this->sort.", Artifacts.". $this->order_col ." ".$this->sort;
+//echo "$sql";
+
+		$result=db_query($sql);//,($this->max_rows),$this->offset);
+		$rows = db_numrows($result);
+		$this->fetched_rows=$rows;
+		if (db_error()) {
+			$this->setError('Database Error: '.db_error());
+			return false;
+		} else {
+			while ($arr =& db_fetch_array($result)) {
+				$this->artifacts[] = new Artifact($this->ArtifactType, $arr);
+			}
+		}
+		return $this->artifacts;
+	}
+
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/tracker/ArtifactFile.class
===================================================================
--- trunk/gforge_base/gforge/common/tracker/ArtifactFile.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/tracker/ArtifactFile.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,325 @@
+<?php
+/**
+ * ArtifactFile.class - Class to handle files within an artifact
+ *
+ * Copyright 1999-2001 (c) VA Linux Systems
+ *
+ * @version   $Id: ArtifactFile.class 5527 2006-06-05 20:10:10Z lo-lan-do $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+require_once('common/include/Error.class');
+
+/**
+*       Factory method which creates an ArtifactFile from an artifactFile ID
+*
+*       @param int      The artifactFile ID
+*       @param array    The result array, if it's passed in
+*       @return object  Artifact object
+*/
+function &artifactfile_get_object($artifact_file_id,$data=false) {
+	global $ARTIFACTFILE_OBJ;
+	if (!isset($ARTIFACTFILE_OBJ["_".$artifact_file_id."_"])) {
+		if ($data) {
+		//the db result handle was passed in
+		} else {
+			$res=db_query("SELECT * FROM artifact_file_user_vw WHERE id='$artifact_file_id'");
+			if (db_numrows($res) <1 ) {
+				$ARTIFACTFILE_OBJ["_".$artifact_file_id."_"]=false;
+				return false;
+			}
+			$data =& db_fetch_array($res);
+		}
+		$Artifact =& artifact_get_object($data["artifact_id"]);
+		$ARTIFACTFILE_OBJ["_".$artifact_file_id."_"]= new ArtifactFile($Artifact,$data);
+	}
+	return $ARTIFACTFILE_OBJ["_".$artifact_file_id."_"];
+}
+
+
+class ArtifactFile extends Error {
+
+	/** 
+	 * The artifact object.
+	 *
+	 * @var		object	$Artifact.
+	 */
+	var $Artifact; //object
+
+	/**
+	 * Array of file data
+	 *
+	 * @var		array	$data_array
+	 */
+	var $data_array;
+
+	/**
+	 *  ArtifactFile - constructor.
+	 *
+	 *	@param	object	The Artifact object.
+	 *  @param	array	(all fields from artifact_file_user_vw) OR id from database.
+	 *  @return	boolean	success.
+	 */
+	function ArtifactFile(&$Artifact, $data=false) {
+		$this->Error(); 
+
+		//was Artifact legit?
+		if (!$Artifact || !is_object($Artifact)) {
+			$this->setError('ArtifactFile: No Valid Artifact');
+			return false;
+		}
+		//did ArtifactType have an error?
+		if ($Artifact->isError()) {
+			$this->setError('ArtifactFile: '.$Artifact->getErrorMessage());
+			return false;
+		}
+		$this->Artifact =& $Artifact;
+
+		if ($data) {
+			if (is_array($data)) {
+				$this->data_array =& $data;
+				return true;
+			} else {
+				if (!$this->fetchData($data)) {
+					return false;
+				} else {
+					return true;
+				}
+			}
+		}
+	}
+
+	/**
+	 *	create - create a new item in the database.
+	 *
+	 *	@param	string	Filename of the item.
+	 *	@param	string	Item filetype.
+	 *	@param	string	Item filesize.
+	 *	@param	binary	Binary item data.
+	 *	@param	string	Item description.
+	 *  @return id on success / false on failure.
+	 */
+	function create($filename, $filetype, $filesize, $bin_data, $description='None') {
+
+		global $Language;
+		// Some browsers don't supply mime type if they don't know it
+		if (!$filetype) {
+			// Let's be on safe side?
+			$filetype = 'application/octet-stream';
+		}
+
+		//
+		//	data validation
+		//
+		if (!$filename || !$filetype || !$filesize || !$bin_data) {
+			//echo '<P>|'.$filename.'|'.$filetype.'|'.$filesize.'|'.$bin_data.'|';
+			$this->setError($Language->getText('tracker_artifactfile','required_fields'));
+			return false;
+		}
+
+		if (session_loggedin()) {
+			$userid=user_getid();
+		} else {
+			$userid=100;
+		}
+
+		// If $filetype is "text/plain", $bin_data convert UTF-8 encoding.
+		if (strcasecmp($filetype,"text/plain") === 0 &&
+		    function_exists('mb_convert_encoding') &&
+		    function_exists('mb_detect_encoding')) {
+			$bin_data = mb_convert_encoding($bin_data,'UTF-8',mb_detect_encoding($bin_data, "auto"));
+			$filesize = strlen($bin_data);
+		}
+
+		db_begin();
+
+		$res=db_query("INSERT INTO artifact_file
+			(artifact_id,description,bin_data,filename,filesize,filetype,adddate,submitted_by)
+			VALUES 
+			('".$this->Artifact->getID()."','$description','". base64_encode($bin_data) ."','$filename',
+			'$filesize','$filetype','". time() ."','$userid')"); 
+
+		$id=db_insertid($res,'artifact_file','id');
+
+		if (!$res || !$id) {
+			db_rollback();
+			$this->setError('ArtifactFile: '.db_error());
+			return false;
+		} else {
+/*
+//
+//	skip this unless we need it later - save a db query
+//
+			//
+			//	Now set up our internal data structures
+			//
+			if (!$this->fetchData($id)) {
+				db_rollback();
+				return false;
+			}
+*/
+			db_commit();
+			$this->Artifact->addHistory('File Added',$id.': '.$filename);
+			$this->clearError();
+			return $id;
+		}
+	}
+
+	/**
+	 *	delete - delete this artifact file from the db.
+	 *
+	 *	@return	boolean	success.
+	 */
+	function delete() {
+		if (!$this->Artifact->ArtifactType->userIsTechnician()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		$res=db_query("DELETE FROM artifact_file WHERE id='". $this->getID() ."'");
+		if (!$res || db_affected_rows($res) < 1) {
+			$this->setError('ArtifactFile: Unable to Delete');
+			return false;
+		} else {
+			$this->Artifact->addHistory('File Deleted',$this->getID().': '.$this->getName());
+			return true;
+		}
+	}
+
+	/**
+	 *	fetchData - re-fetch the data for this ArtifactFile from the database.
+	 *
+	 *	@param	int	The file_id.
+	 *	@return	boolean	success.
+	 */
+	function fetchData($id) {
+		$res=db_query("SELECT * FROM artifact_file_user_vw WHERE id='$id'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('ArtifactFile: Invalid ArtifactFile ID');
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	getArtifact - get the Artifact Object this ArtifactFile is associated with.
+	 *
+	 *	@return object	Artifact.
+	 */
+	function &getArtifact() {
+		return $this->Artifact;
+	}
+	
+	/**
+	 *	getID - get this ArtifactFile's ID.
+	 *
+	 *	@return	int	The id #.
+	 */
+	function getID() {
+		return $this->data_array['id'];
+	}
+
+	/**
+	 *	getName - get the filename.
+	 *
+	 *	@return string filename.
+	 */
+	function getName() {
+		return $this->data_array['filename'];
+	}
+
+	/**
+	 *	getType - get the type.
+	 *
+	 *	@return string type.
+	 */
+	function getType() {
+		return $this->data_array['filetype'];
+	}
+
+	/**
+	 *	getData - get the binary data from the db.
+	 *
+	 *	@return binary.
+	 */
+	function &getData() {
+		return base64_decode($this->data_array['bin_data']);
+	}
+
+	/**
+	 *	getSize - get the size.
+	 *
+	 *	@return int size.
+	 */
+	function getSize() {
+		return $this->data_array['filesize'];
+	}
+
+	/**
+	 *	getDescription - get the description.
+	 *
+	 *	@return string description.
+	 */
+	function getDescription() {
+		return $this->data_array['description'];
+	}
+
+	/**
+	 *	getDate - get the date file was added.
+	 *
+	 *	@return int unix time.
+	 */
+	function getDate() {
+		return $this->data_array['adddate'];
+	}
+
+	/**
+	 *	getSubmittedBy - get the user_id of the submitter.
+	 *
+	 *	@return int user_id.
+	 */
+	function getSubmittedBy() {
+		return $this->data_array['submitted_by'];
+	}
+
+	/**
+	 *	getSubmittedRealName - get the real name of the submitter.
+	 *
+	 *	@return	string	name.
+	 */
+	function getSubmittedRealName() {
+		return $this->data_array['realname'];
+	}
+
+	/**
+	 *	getSubmittedUnixName - get the unix name of the submitter.
+	 *
+	 *	@return	string	unixname.
+	 */
+	function getSubmittedUnixName() {
+		return $this->data_array['user_name'];
+	}
+
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/tracker/ArtifactFromID.class
===================================================================
--- trunk/gforge_base/gforge/common/tracker/ArtifactFromID.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/tracker/ArtifactFromID.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,79 @@
+<?php
+/**
+ * GForge Tracker Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: ArtifactFromID.class 5527 2006-06-05 20:10:10Z lo-lan-do $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+
+require_once ('common/include/Error.class');
+
+class ArtifactFromID extends Error {
+
+//artifact_vw
+
+	var $Group;
+	var $ArtifactType;
+	var $Artifact;
+
+	function ArtifactFromID($id, $data = false) {
+		if ($data) {
+			$art_arr =& $data;
+		} else {
+			$res=db_query("SELECT * FROM artifact_vw WHERE artifact_id='$id'");
+			if (!$res || db_numrows($res) < 1) {
+				$this->setError("Invalid Artifact ID");
+				return false;
+			} else {
+				$art_arr =& db_fetch_array($res);
+			}
+		} 
+
+		$at = artifactType_get_object($art_arr['group_artifact_id']);
+		if (!$at || !is_object($at)) {
+			$this->setError("Could Not Create ArtifactType");
+			return false;
+		} elseif ($at->isError()) {
+			$this->setError($at->getErrorMessage());
+			return false;
+		}
+		$this->ArtifactType =& $at;
+
+		$a = artifact_get_object($id,$art_arr);
+		if (!$a || !is_object($a)) {
+			$this->setError("Could Not Create Artifact");
+			return false;
+		} elseif ($a->isError()) {
+			$this->setError($a->getErrorMessage());
+			return false;
+		}
+		$this->Artifact =& $a;
+		return true;
+	}
+
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/tracker/ArtifactHistory.class
===================================================================
--- trunk/gforge_base/gforge/common/tracker/ArtifactHistory.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/tracker/ArtifactHistory.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,179 @@
+<?php
+/**
+ * ArtifactHistory.class - Class to handle artifact history (unused-waiting for SOAP)
+ *
+ * Copyright 2004 (c) GForge, LLC
+ *
+ * @version   $Id: ArtifactHistory.class 5527 2006-06-05 20:10:10Z lo-lan-do $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+require_once('common/include/Error.class');
+
+class ArtifactHistory extends Error {
+
+	/** 
+	 * The artifact object.
+	 *
+	 * @var		object	$Artifact.
+	 */
+	var $Artifact; //object
+
+	/**
+	 * Array of artifact data.
+	 *
+	 * @var		array	$data_array.
+	 */
+	var $data_array;
+
+	/**
+	 *  ArtifactHistory - constructor.
+	 *
+	 *	@param	object	Artifact object.
+	 *  @param	array	(all fields from artifact_history_user_vw) OR id from database.
+	 *  @return	boolean	success.
+	 */
+	function ArtifactHistory(&$Artifact, $data=false) {
+		$this->Error(); 
+
+		//was Artifact legit?
+		if (!$Artifact || !is_object($Artifact)) {
+			$this->setError('ArtifactHistory: No Valid Artifact');
+			return false;
+		}
+		//did Artifact have an error?
+		if ($Artifact->isError()) {
+			$this->setError('ArtifactHistory: '.$Artifact->getErrorMessage());
+			return false;
+		}
+		$this->Artifact =& $Artifact;
+
+		if ($data) {
+			if (is_array($data)) {
+				$this->data_array =& $data;
+				return true;
+			} else {
+				if (!$this->fetchData($data)) {
+					return false;
+				} else {
+					return true;
+				}
+			}
+		}
+	}
+
+	/**
+	 *	create - create a new item in the database.
+	 *
+	 *	@param	string	Item name.
+	 *	@param	int		User_id of assignee.
+	 *  @return id on success / false on failure.
+	 * /
+	function create($name, $auto_assign_to) {
+		global $Language;
+		
+		//
+		//	data validation
+		//
+		if (!$name || !$auto_assign_to) {
+			$this->setError($Language->getText('artifact_category','required_fields'));
+			return false;
+		}
+		if (!$this->Artifact->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		$sql="INSERT INTO artifact_category (group_artifact_id,category_name,auto_assign_to) 
+			VALUES ('".$this->Artifact->getID()."','".htmlspecialchars($name)."','$auto_assign_to')";
+
+		$result=db_query($sql);
+
+		if ($result && db_affected_rows($result) > 0) {
+			$this->clearError();
+			return true;
+		} else {
+			$this->setError(db_error());
+			return false;
+		}
+
+		//	Now set up our internal data structures
+		if (!$this->fetchData($id)) {
+			return false;
+		}
+	}*/
+
+	/**
+	 *	fetchData - re-fetch the data for this ArtifactHistory from the database.
+	 *
+	 *	@param	int		ID of the category.
+	 *	@return	boolean	success.
+	 */
+	function fetchData($id) {
+		$res=db_query("SELECT * FROM artifact_category WHERE id='$id'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('ArtifactHistory: Invalid ArtifactHistory ID');
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	getArtifact - get the Artifact Object this ArtifactHistory is associated with.
+	 *
+	 *	@return object	Artifact.
+	 */
+	function &getArtifact() {
+		return $this->Artifact;
+	}
+	
+	/**
+	 *	getID - get this ArtifactHistory's ID.
+	 *
+	 *	@return	int	The id #.
+	 */
+	function getID() {
+		return $this->data_array['id'];
+	}
+
+	/**
+	 *	getName - get the name.
+	 *
+	 *	@return	string	The name.
+	 * /
+	function getName() {
+		return $this->data_array['category_name'];
+	}
+
+	/**
+	 *	getAssignee - get the user_id of the person to assign this category to.
+	 *
+	 *	@return int user_id.
+	 * /
+	function getAssignee() {
+		return $this->data_array['auto_assign_to'];
+	}
+	*/
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/tracker/ArtifactMessage.class
===================================================================
--- trunk/gforge_base/gforge/common/tracker/ArtifactMessage.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/tracker/ArtifactMessage.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,204 @@
+<?php
+/**
+ * ArtifactMessage.class - Class to handle artifact messages.
+ *
+ * Copyright 2004 (c) GForge, LLC
+ *
+ * @version   $Id: ArtifactMessage.class 5527 2006-06-05 20:10:10Z lo-lan-do $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+require_once('common/include/Error.class');
+
+class ArtifactMessage extends Error {
+
+	/** 
+	 * The artifact object.
+	 *
+	 * @var		object	$Artifact.
+	 */
+	var $Artifact; //object
+
+	/**
+	 * Array of artifact data.
+	 *
+	 * @var		array	$data_array.
+	 */
+	var $data_array;
+
+	/**
+	 *  ArtifactMessage - constructor.
+	 *
+	 *	@param	object	Artifact object.
+	 *  @param	array	(all fields from artifact_history_user_vw) OR id from database.
+	 *  @return	boolean	success.
+	 */
+	function ArtifactMessage(&$Artifact, $data=false) {
+		$this->Error(); 
+
+		//was Artifact legit?
+		if (!$Artifact || !is_object($Artifact)) {
+			$this->setError('ArtifactMessage: No Valid Artifact');
+			return false;
+		}
+		//did Artifact have an error?
+		if ($Artifact->isError()) {
+			$this->setError('ArtifactMessage: '.$Artifact->getErrorMessage());
+			return false;
+		}
+		$this->Artifact =& $Artifact;
+
+		if ($data) {
+			if (is_array($data)) {
+				$this->data_array =& $data;
+				return true;
+			} else {
+				if (!$this->fetchData($data)) {
+					return false;
+				} else {
+					return true;
+				}
+			}
+		}
+	}
+
+	/**
+	 *	create - create a new item in the database.
+	 *
+	 *	@param	string	Body.
+	 *	@param	string	email of submitter (obsolete?).
+	 *  @return id on success / false on failure.
+	 */
+	function create($body,$by=false) {
+		global $Language;
+		
+		if (!$body) {
+			$this->setMissingParamsError();
+			return false;
+		}
+
+		if (session_loggedin()) {
+			$user_id=user_getid();
+			$user =& user_get_object($user_id);
+			if (!$user || !is_object($user)) {
+				$this->setError('ERROR - Logged In User Bug Could Not Get User Object');
+				return false;
+			}
+			$body=$Language->getText('tracker_artifact','logged_in_yes')." \nuser_id=$user_id\n\n".$body;
+
+			//  we'll store this email even though it will likely never be used -
+			//  since we have their correct user_id, we can join the USERS table to get email
+			$by=$user->getEmail();
+		} else {
+			$body=$Language->getText('tracker_artifact','logged_in_no')." \n\n".$body;
+			$user_id=100;
+			if (!$by || !validate_email($by)) {
+				$this->setMissingParamsError();
+				return false;
+			}
+		}
+
+		$sql="insert into artifact_message (artifact_id,submitted_by,from_email,adddate,body) 
+			VALUES ('". $this->Artifact->getID() ."','$user_id','$by','". time() ."','". htmlspecialchars($body). "')";
+		$res = db_query($sql);
+
+		if (!$res) {
+			$this->setError(db_error());
+			return false;
+		} else {
+			$id=db_insertid($res,'artifact_message','id');
+		}
+
+		//
+		//	Now set up our internal data structures
+		//
+		if (!$this->fetchData($id)) {
+			return false;
+		}
+		return $id;
+	}
+
+	/**
+	 *	fetchData - re-fetch the data for this ArtifactMessage from the database.
+	 *
+	 *	@param	int		ID of the category.
+	 *	@return	boolean	success.
+	 */
+	function fetchData($id) {
+		$res=db_query("SELECT * FROM artifact_message_user_vw WHERE id='$id'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('ArtifactMessage: Invalid ArtifactMessage ID');
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	getArtifact - get the Artifact Object this ArtifactMessage is associated with.
+	 *
+	 *	@return object	Artifact.
+	 */
+	function &getArtifact() {
+		return $this->Artifact;
+	}
+	
+	/**
+	 *	getID - get this ArtifactMessage's ID.
+	 *
+	 *	@return	int	The id #.
+	 */
+	function getID() {
+		return $this->data_array['id'];
+	}
+
+	/**
+	 *	getBody - get the message body.
+	 *
+	 *	@return	string	The message body.
+	 */
+	function getBody() {
+		return $this->data_array['body'];
+	}
+
+	/**
+	 *	getAddDate - get the date this message was added.
+	 *
+	 *	@return int adddate.
+	 */
+	function getAddDate() {
+		return $this->data_array['addate'];
+	}
+
+	/**
+	 *	getUserID - get the ID of the person who posted this.
+	 *
+	 *	@return int user_id.
+	 */
+	function getUserID() {
+		return $this->data_array['user_id'];
+	}
+
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/tracker/ArtifactQuery.class
===================================================================
--- trunk/gforge_base/gforge/common/tracker/ArtifactQuery.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/tracker/ArtifactQuery.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,524 @@
+<?php
+/**
+ * ArtifactQuery.class - Class to handle user defined artifacts
+ *
+ * Copyright 2005 (c) GForge Group, LLC; Anthony J. Pugliese,
+ *
+ * @version   $Id: ArtifactQuery.class 5527 2006-06-05 20:10:10Z lo-lan-do $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+require_once('common/include/Error.class');
+
+define('ARTIFACT_QUERY_ASSIGNEE',1);
+define('ARTIFACT_QUERY_STATE',2);
+define('ARTIFACT_QUERY_MODDATE',3);
+define('ARTIFACT_QUERY_EXTRAFIELD',4);
+define('ARTIFACT_QUERY_SORTCOL',5);
+define('ARTIFACT_QUERY_SORTORD',6);
+define('ARTIFACT_QUERY_OPENDATE',7);
+define('ARTIFACT_QUERY_CLOSEDATE',8);
+
+require_once('common/tracker/ArtifactType.class');
+
+class ArtifactQuery extends Error {
+	/** 
+	 * The artifact type object.
+	 *
+	 * @var		object	$ArtifactType.
+	 */
+	var $ArtifactType; //object
+
+	/**
+	 * Array of artifact data.
+	 *
+	 * @var		array	$data_array.
+	 */
+	var $data_array;
+
+	/**
+	 *	ArtifactQuery - Constructer
+	 *
+	 *	@param	object	ArtifactType object.
+	 *	@param 	
+	 *  	@return	boolean	success.
+	 */
+	function ArtifactQuery(&$ArtifactType, $data = false) {
+		$this->Error(); 
+
+		//was ArtifactType legit?
+		if (!$ArtifactType || !is_object($ArtifactType)) {
+			$this->setError('ArtifactQuery: No Valid ArtifactType');
+			return false;
+		}
+		//did ArtifactType have an error?
+		if ($ArtifactType->isError()) {
+			$this->setError('ArtifactQuery: '.$ArtifactType->getErrorMessage());
+			return false;
+		}
+		$this->ArtifactType =& $ArtifactType;
+
+		if ($data) {
+			if (is_array($data)) {
+				$this->data_array =& $data;
+				return true;
+			} else {
+				if (!$this->fetchData($data)) {
+					return false;
+				} else {
+					return true;
+				}
+			}
+		}
+	}
+
+	/**
+	 *	create - create a row in the table that stores a saved query for 	 *	a tracker.   
+	 *
+	 *	@param	string	Name of the saved query.
+	 *  	@return 	true on success / false on failure.
+	 */
+	function create($name,$status,$assignee,$moddaterange,$sort_col,$sort_ord,$extra_fields,$opendaterange=0,$closedaterange=0) {
+		global $Language;
+		
+		//
+		//	data validation
+		//
+		if (!$name) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		if (!session_loggedin()) {
+			$this->setError('Must Be Logged In');
+			return false;
+		}
+
+		if ($this->Exist(htmlspecialchars($name))) {
+			$this->setError($Language->getText('artifact_query','exist_query'));
+			return false;
+		}
+
+		$sql="INSERT INTO artifact_query (group_artifact_id,query_name,user_id) 
+			VALUES ('".$this->ArtifactType->getID()."','".htmlspecialchars($name)."','".user_getid()."')";
+
+		db_begin();
+		$result=db_query($sql);
+		if ($result && db_affected_rows($result) > 0) {
+			$this->clearError();
+			$id=db_insertid($result,'artifact_query','artifact_query_id');
+			if (!$id) {
+				$this->setError('Error getting id '.db_error());
+				db_rollback();
+				return false;
+			} else {
+				if (!$this->insertElements($id,$status,$assignee,$moddaterange,$sort_col,$sort_ord,$extra_fields,$opendaterange,$closedaterange)) {
+					db_rollback();
+					return false;
+				}
+			}
+		} else {
+			$this->setError(db_error());
+			db_rollback();
+			return false;
+		}
+		//
+		//	Now set up our internal data structures
+		//
+		if ($this->fetchData($id)) {
+			db_commit();
+			return true;
+		} else {
+			db_rollback();
+			return false;
+		}
+	}
+
+	/**
+	 *	fetchData - re-fetch the data for this ArtifactQuery from the database.
+	 *
+	 *	@param	int		ID of saved query.
+	 *	@return	boolean	success.
+	 */
+	function fetchData($id) {
+		$res=db_query("SELECT * FROM artifact_query WHERE artifact_query_id='$id'");
+		
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('ArtifactQuery: Invalid ArtifactQuery ID'.db_error());
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		$res=db_query("SELECT * FROM artifact_query_fields WHERE artifact_query_id='$id'");
+		unset($this->element_array);
+		while ($arr = db_fetch_array($res)) {
+			//
+			//	Some things may have been saved as comma-separated items
+			//
+			if (strstr($arr['query_field_values'],',')) {
+				$arr['query_field_values']=explode(',',$arr['query_field_values']);
+			}
+			$this->element_array[$arr['query_field_type']][$arr['query_field_id']]=$arr['query_field_values'];
+		}
+		return true;
+	}
+
+	/**
+	 *	getArtifactType - get the ArtifactType Object this ArtifactExtraField is associated with.
+	 *
+	 *	@return object	ArtifactType.
+	 */
+	function &getArtifactType() {
+		return $this->ArtifactType;
+	}
+
+	/**
+	 *
+	 *
+	 */
+	function insertElements($id,$status,$assignee,$moddaterange,$sort_col,$sort_ord,$extra_fields,$opendaterange,$closedaterange) {
+		$res=db_query("DELETE FROM artifact_query_fields WHERE artifact_query_id='$id'");
+		if (!$res) {
+			$this->setError('Deleting Old Elements: '.db_error());
+			return false;
+		}
+		$id = intval($id);
+		$res=db_query("INSERT INTO artifact_query_fields 
+			(artifact_query_id,query_field_type,query_field_id,query_field_values) 
+			VALUES ('$id','".ARTIFACT_QUERY_STATE."','0','".intval($status)."')");
+		if (!$res) {
+			$this->setError('Setting Status: '.db_error());
+			return false;
+		}
+	
+		if (is_array($assignee)) {
+				for($e=0; $e<count($assignee); $e++) {
+					$assignee[$e]=intval($assignee[$e]); 
+				}
+				$assignee=implode(',',$assignee);
+		} else {
+			$assignee = intval($assignee);
+		}	
+		
+		if (preg_match("/[^[:alnum:]_]/", $string)) {
+			$this->setError('ArtifactQuery: not valid sort_col');
+			return false;
+		}
+		
+		if (preg_match("/[^[:alnum:]_]/", $string)) {
+			$this->setError('ArtifactQuery: not valid sort_ord');
+			return false;
+		}
+
+		//CSV LIST OF ASSIGNEES
+		$res=db_query("INSERT INTO artifact_query_fields 
+			(artifact_query_id,query_field_type,query_field_id,query_field_values) 
+			VALUES ('$id','".ARTIFACT_QUERY_ASSIGNEE."','0','".$assignee."')");
+		if (!$res) {
+			$this->setError('Setting Assignee: '.db_error());
+			return false;
+		}
+
+		//MOD DATE RANGE  YYYY-MM-DD YYYY-MM-DD format
+		if ($moddaterange && !$this->validateDateRange($moddaterange)) {
+			$this->setError('Invalid Mod Date Range');
+			return false;
+		}
+		$res=db_query("INSERT INTO artifact_query_fields 
+			(artifact_query_id,query_field_type,query_field_id,query_field_values) 
+			VALUES ('$id','".ARTIFACT_QUERY_MODDATE."','0','".$moddaterange."')");
+		if (!$res) {
+			$this->setError('Setting Last Modified Date Range: '.db_error());
+			return false;
+		}
+
+		//OPEN DATE RANGE YYYY-MM-DD YYYY-MM-DD format
+		if ($opendaterange && !$this->validateDateRange($opendaterange)) {
+			$this->setError('Invalid Open Date Range');
+			return false;
+		}
+		$res=db_query("INSERT INTO artifact_query_fields 
+			(artifact_query_id,query_field_type,query_field_id,query_field_values) 
+			VALUES ('$id','".ARTIFACT_QUERY_OPENDATE."','0','".$opendaterange."')");
+		if (!$res) {
+			$this->setError('Setting Open Date Range: '.db_error());
+			return false;
+		}
+
+		//CLOSE DATE RANGE YYYY-MM-DD YYYY-MM-DD format
+		if ($closedaterange && !$this->validateDateRange($closedaterange)) {
+			$this->setError('Invalid Close Date Range');
+			return false;
+		}
+		$res=db_query("INSERT INTO artifact_query_fields 
+			(artifact_query_id,query_field_type,query_field_id,query_field_values) 
+			VALUES ('$id','".ARTIFACT_QUERY_CLOSEDATE."','0','".$closedaterange."')");
+		if (!$res) {
+			$this->setError('Setting Close Date Range: '.db_error());
+			return false;
+		}
+
+		// SORT COLUMN
+		$res=db_query("INSERT INTO artifact_query_fields 
+			(artifact_query_id,query_field_type,query_field_id,query_field_values) 
+			VALUES ('$id','".ARTIFACT_QUERY_SORTCOL."','0','".$sort_col."')");
+		if (!$res) {
+			$this->setError('Setting Sort Col: '.db_error());
+			return false;
+		}
+		$res=db_query("INSERT INTO artifact_query_fields 
+			(artifact_query_id,query_field_type,query_field_id,query_field_values) 
+			VALUES ('$id','".ARTIFACT_QUERY_SORTORD."','0','".$sort_ord."')");
+		if (!$res) {
+			$this->setError('Setting Sort Order: '.db_error());
+			return false;
+		}
+
+		if (!$extra_fields) {
+			$extra_fields=array();
+		}
+		
+		$keys=array_keys($extra_fields);
+		$vals=array_values($extra_fields);
+		for ($i=0; $i<count($keys); $i++) {
+			if (!$vals[$i]) {
+				continue;
+			}
+			//
+			//	Checkboxes and multi-select may be arrays so store it comma-separated
+			//
+			if (is_array($vals[$i])) {
+				for($e=0; $e<count($vals[$i]); $e++) {
+					$vals[$i][$e]=intval($vals[$i][$e]); 
+				}
+				$vals[$i]=implode(',',$vals[$i]);
+			} else {
+				$vals[$i] =	 intval($vals[$i]);
+			}
+			$res=db_query("INSERT INTO artifact_query_fields 
+				(artifact_query_id,query_field_type,query_field_id,query_field_values) 
+				VALUES ('$id','".ARTIFACT_QUERY_EXTRAFIELD."','".((int)$keys[$i]) ."','". $vals[$i] ."')");
+			if (!$res) {
+				$this->setError('Setting values: '.db_error());
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 *	getID - get this ArtifactQuery ID.
+	 *
+	 *	@return	int	The id #.
+	 */
+	function getID() {
+		return $this->data_array['artifact_query_id'];
+	}
+
+	/**
+	 *	getName - get the name.
+	 *
+	 *	@return	string	The name.
+	 */
+	function getName() {
+		return $this->data_array['query_name'];
+	}
+
+	/**
+	 *	getSortCol - the column that you're sorting on
+	 *
+	 *	@return	string	The column name.
+	 */
+	function getSortCol() {
+		return $this->element_array[ARTIFACT_QUERY_SORTCOL][0];
+	}
+
+	/**
+	 *	getSortOrd - ASC or DESC
+	 *
+	 *	@return	string	ASC or DESC
+	 */
+	function getSortOrd() {
+		return $this->element_array[ARTIFACT_QUERY_SORTORD][0];
+	}
+
+	/**
+	 *	getModDateRange - get the range of dates to include in a query
+	 *
+	 *	@return	string	mod date range.
+	 */
+	function getModDateRange() {
+		if ($this->element_array[ARTIFACT_QUERY_MODDATE][0]) {
+			return $this->element_array[ARTIFACT_QUERY_MODDATE][0];
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 *	getOpenDateRange - get the range of dates to include in a query
+	 *
+	 *	@return	string	Open date range.
+	 */
+	function getOpenDateRange() {
+		if ($this->element_array[ARTIFACT_QUERY_OPENDATE][0]) {
+			return $this->element_array[ARTIFACT_QUERY_OPENDATE][0];
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 *	getCloseDateRange - get the range of dates to include in a query
+	 *
+	 *	@return	string	Close date range.
+	 */
+	function getCloseDateRange() {
+		if ($this->element_array[ARTIFACT_QUERY_CLOSEDATE][0]) {
+			return $this->element_array[ARTIFACT_QUERY_CLOSEDATE][0];
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 *	getAssignee
+	 *
+	 *	@return	string	Assignee ID
+	 */
+	function getAssignee() {
+		return $this->element_array[ARTIFACT_QUERY_ASSIGNEE][0];
+	}
+
+	/**
+	 *	getStatus
+	 *
+	 *	@return	string	Status ID
+	 */
+	function getStatus() {
+		return $this->element_array[ARTIFACT_QUERY_STATE][0];
+	}
+
+	/**
+	 *	getExtraFields - complex multi-dimensional array of extra field IDs/Vals
+	 *
+	 *	@return	array	Complex Array
+	 */
+	function getExtraFields() {
+		return $this->element_array[ARTIFACT_QUERY_EXTRAFIELD];
+	}
+
+	/**
+	 *	validateDateRange - validate a date range in this format '1999-05-01 1999-06-01'.
+	 *
+	 *	@return	boolean	true/false.
+	 */
+	function validateDateRange($daterange) {
+		return preg_match('/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{4}-[0-9]{2}-[0-9]{2}/',$daterange);
+	}
+
+	/**
+	 *  update - update a row in the table used to query names 
+	 *  for a tracker.  
+	 *
+	 *  	@param	int	 Id of the saved query
+	 *	@param	string	The name of the saved query
+	 *  @return	boolean	success.
+	 */
+	function update($name,$status,$assignee,$moddaterange,$sort_col,$sort_ord,$extra_fields,$opendaterange='',$closedaterange='') {
+		global $Language;
+		if (!$name) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		if (!session_loggedin()) {
+			$this->setError('Must Be Logged In');
+			return false;
+		}
+		if (!$this->Exist(htmlspecialchars($name))) {
+			$this->setError($Language->getText('artifact_query','not_exist_query'));
+			return false;
+		}
+		$sql="UPDATE artifact_query
+			SET 
+			query_name='".htmlspecialchars($name)."'
+			WHERE artifact_query_id='".$this->getID()."'
+			AND user_id='".user_getid()."'";
+		db_begin();
+		$result=db_query($sql);
+		if ($result && db_affected_rows($result) > 0) {
+			if (!$this->insertElements($this->getID(),$status,$assignee,$moddaterange,$sort_col,$sort_ord,$extra_fields,$opendaterange,$closedaterange)) {
+				db_rollback();
+				return false;
+			} else {
+				db_commit();
+				$this->fetchData($this->getID());
+				return true;
+			}
+		} else {
+			$this->setError('Error Updating: '.db_error());
+			db_rollback();
+			return false;
+		}
+	}
+
+	/**
+	 *  makeDefault - set this as the default query
+	 *
+	 *  @return	boolean	success.
+	 */
+	function makeDefault() {
+		if (!session_loggedin()) {
+			$this->setError('Must Be Logged In');
+			return false;
+		}
+		$usr =& session_get_user();
+		return $usr->setPreference('art_query'.$this->ArtifactType->getID(),$this->getID());
+	}
+
+	function delete() {
+		$res=db_query("DELETE FROM artifact_query WHERE artifact_query_id='".$this->getID()."'
+            AND user_id='".user_getid()."'");
+		$res=db_query("DELETE FROM user_preferences WHERE preference_value='".$this->getID()."'
+            AND preference_name 'art_query".$this->ArtifactType->getID()."'");
+		unset($this->data_array);
+		unset($this->element_array);
+	}
+
+	/**
+	 *  Exist - check if already exist a query with the same name , user_id and artifact_id
+	 *
+	 *  @return	boolean	exist
+	 */
+	function Exist($name) {
+		$user_id = user_getid();
+		$art_id = $this->ArtifactType->getID();
+		$sql = "SELECT * FROM artifact_query WHERE group_artifact_id = '$art_id' AND query_name = '$name' AND user_id = '$user_id'";
+		$res = db_query($sql);
+		if (db_numrows($res)>0) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>


Property changes on: trunk/gforge_base/gforge/common/tracker/ArtifactQuery.class
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/gforge_base/gforge/common/tracker/ArtifactQueryFactory.class
===================================================================
--- trunk/gforge_base/gforge/common/tracker/ArtifactQueryFactory.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/tracker/ArtifactQueryFactory.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,94 @@
+<?php
+/**
+ * GForge Tracker Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: ArtifactQueryFactory.class 5527 2006-06-05 20:10:10Z lo-lan-do $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+ 
+require_once('common/include/Error.class');
+require_once('common/tracker/ArtifactQuery.class');
+
+class ArtifactQueryFactory extends Error {
+	/**
+	 * The ArtifactType object.
+	 *
+	 * @var	 object  $Group.
+	 */
+	var $ArtifactType;
+
+	/**
+	 * The ArtifactQueries array.
+	 *
+	 * @var	 array	ArtifactQueries.
+	 */
+	var $ArtifactQueries = null;
+
+	/**
+	 *  Constructor.
+	 *
+	 *	@param	object	The Group object to which this ArtifactQueryFactory is associated
+	 *	@return	boolean	success.
+	 */
+	function ArtifactQueryFactory(&$ArtifactType) {
+		$this->Error();
+		if (!$ArtifactType || !is_object($ArtifactType)) {
+			$this->setError('ArtifactQueryFactory:: No ArtifactType Object');
+			return false;
+		}
+		if ($ArtifactType->isError()) {
+			$this->setError('ArtifactQueryFactory:: '.$ArtifactType->getErrorMessage());
+			return false;
+		}
+		$this->ArtifactType =& $ArtifactType;
+
+
+		return true;
+	}
+	
+	function& getArtifactQueries() {
+		if (!is_null($this->ArtifactQueries)) {
+			return $this->ArtifactQueries;
+		}
+		
+		$this->ArtifactQueries = array();
+		
+		$res = db_query("SELECT * FROM artifact_query WHERE user_id='".user_getid()."' ".
+					"AND group_artifact_id='".$this->ArtifactType->getID()."'");
+		if (!$res) {
+			$this->setError("ArtifactQueryFactory:: Database error");
+		}
+		
+		while ($data = db_fetch_array($res)) {
+			$artifactQuery = new ArtifactQuery($this->ArtifactType, $data["artifact_query_id"]);
+			$this->ArtifactQueries[] = $artifactQuery;
+		}
+		
+		return $this->ArtifactQueries;
+	}
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/tracker/ArtifactType.class
===================================================================
--- trunk/gforge_base/gforge/common/tracker/ArtifactType.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/tracker/ArtifactType.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,1237 @@
+<?php
+/**
+ * ArtifactType.class - Class to artifact an type
+ *
+ * Copyright 1999-2001 (c) VA Linux Systems
+ * The rest Copyright 2002-2004 (c) GForge Team
+ * http://gforge.org/
+ *
+ * @version   $Id: ArtifactType.class 5621 2006-07-23 16:27:16Z tperdue $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+require_once('common/include/Error.class');
+
+	/**
+	* Gets an ArtifactType object from the artifact type id
+	* 
+	* @param artType_id	the ArtifactType id
+	* @param res	the DB handle if passed in (optional)
+	* @return	the ArtifactType object	
+	*/
+	function &artifactType_get_object($artType_id,$res=false) {
+		global $ARTIFACTTYPE_OBJ;
+		if (!isset($ARTIFACTTYPE_OBJ["_".$artType_id."_"])) {
+			if ($res) {
+				//the db result handle was passed in
+			} else {
+				$res=db_query("SELECT * FROM artifact_group_list_vw
+						WHERE group_artifact_id='$artType_id'");
+			}
+			if (!$res || db_numrows($res) < 1 ){
+				$ARTIFACTTYPE_OBJ["_".$artType_id."_"]=false;
+			} else {
+				$data =& db_fetch_array($res);
+				$Group =& group_get_object($data["group_id"]);
+				$ARTIFACTTYPE_OBJ["_".$artType_id."_"]= new ArtifactType($Group,$data["group_artifact_id"],$data);
+			}
+		}
+		return $ARTIFACTTYPE_OBJ["_".$artType_id."_"];
+	}	
+
+class ArtifactType extends Error {
+
+	/**
+	 * The Group object.
+	 *
+	 * @var		object	$Group.
+	 */
+	var $Group; //group object
+
+	/**
+	 * extra_fields 3d array - the IDs and Names of the extra fields
+	 *
+	 * @var		array extra_fields;
+	 */
+	var $extra_fields = array();
+
+	/**
+	 * extra_field[extra_field_id] array - the IDs and Names of elements on the extra fields
+	 *
+	 * @var		array	extra_field
+	 */
+	var $extra_field;
+	
+	/**
+	 * Options	db resource ID.
+	 *
+	 * @var		int		$options_res.
+	 */
+	var $options_res;
+
+	/**
+	 * Choice name	db resource ID.
+	 *
+	 * @var		int		$choice_name_res.
+	 */
+	var $choice_name_res;
+
+	/**
+	 * Current user permissions.
+	 *
+	 * @var		int		$current_user_perm.
+	 */
+	var $current_user_perm;
+
+	/**
+	 * Technicians db resource ID.
+	 *
+	 * @var		int		$technicians_res.
+	 */
+	var $technicians_res;
+
+	/**
+	 * Status db resource ID.
+	 *
+	 * @var		int		$status_res.
+	 */
+	var $status_res;
+
+	/**
+	 * Canned responses resource ID.
+	 *
+	 * @var		int		$cannecresponses_res.
+	 */
+	var $cannedresponses_res;
+
+	/**
+	 * Array of artifact data.
+	 *
+	 * @var		array	$data_array.
+	 */
+	var $data_array;
+
+	/**
+	 * Array of element names so they only have to be fetched once from db.
+	 *
+	 * @var		array	$data_array.
+	 */
+	var	$element_name;
+
+	/**
+	 * Array of element status so they only have to be fetched once from db.
+	 *
+	 * @var		array	$data_array.
+	 */
+	var	$element_status;
+
+	/**
+	 *	ArtifactType - constructor.
+	 *
+	 *	@param	object	The Group object.
+	 *	@param	int		The id # assigned to this artifact type in the db.
+	 *  @param	array	The associative array of data.
+	 *	@return boolean	success.
+	 */
+	function ArtifactType(&$Group,$artifact_type_id=false, $arr=false) {
+		$this->Error();
+		if (!$Group || !is_object($Group)) {
+			$this->setError('No Valid Group Object');
+			return false;
+		}
+		if ($Group->isError()) {
+			$this->setError('ArtifactType: '.$Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $Group;
+		if ($artifact_type_id) {
+			if (!$arr || !is_array($arr)) {
+				if (!$this->fetchData($artifact_type_id)) {
+					return false;
+				}
+			} else {
+				$this->data_array =& $arr;
+				if ($this->data_array['group_id'] != $this->Group->getID()) {
+					$this->setError('Group_id in db result does not match Group Object');
+					$this->data_array = null;
+					return false;
+				}
+			}
+			//
+			//  Make sure they can even access this object
+			//
+			if (!$this->userCanView()) {
+				$this->setPermissionDeniedError();
+				$this->data_array = null;
+				return false;
+			}
+		}
+	}
+
+	/**
+	 *	create - use this to create a new ArtifactType in the database.
+	 *
+	 *	@param	string	The type name.
+	 *	@param	string	The type description.
+	 *  @param  bool	(1) true (0) false - viewable by general public.
+	 *  @param  bool	(1) true (0) false - whether non-logged-in users can submit.
+	 *	@param	bool	(1) true (0) false - whether to email on all updates.
+	 *	@param	string	The address to send new entries and updates to.
+	 *	@param	int		Days before this item is considered overdue.
+	 *	@param	bool	(1) trye (0) false - whether the resolution box should be shown.
+	 *	@param	string	Free-form string that project admins can place on the submit page.
+	 *	@param	string	Free-form string that project admins can place on the browse page.
+	 *	@param	int		(1) bug tracker, (2) Support Tracker, (3) Patch Tracker, (4) features (0) other.
+	 *	@return id on success, false on failure.
+	 */
+	function create($name,$description,$is_public,$allow_anon,$email_all,$email_address,
+		$due_period,$use_resolution,$submit_instructions,$browse_instructions,$datatype=0) {
+
+		global $Language;
+		$perm =& $this->Group->getPermission( session_get_user() );
+
+		if (!$perm || !is_object($perm) || !$perm->isArtifactAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		if (!$name || !$description || !$due_period) {
+			$this->setError($Language->getText('tracker_artifacttype','required_fields'));
+			return false;
+		}
+		
+		if ($email_address) {
+			$invalid_emails = validate_emails($email_address);
+			if (count($invalid_emails) > 0) {
+				$this->SetError($Language->getText('tracker_artifacttype','invalid_emails').': '.implode(',',$invalid_emails));
+				return false;
+			}
+		}
+
+		$use_resolution = ((!$use_resolution) ? 0 : $use_resolution);
+		$is_public = ((!$is_public) ? 0 : $is_public);
+		$allow_anon = ((!$allow_anon) ? 0 : $allow_anon);
+		$email_all = ((!$email_all) ? 0 : $email_all);
+
+
+		$sql="INSERT INTO 
+			artifact_group_list 
+			(group_id,
+			name,
+			description,
+			is_public,
+			allow_anon,
+			email_all_updates,
+			email_address,
+			due_period,
+			status_timeout,
+			submit_instructions,
+			browse_instructions,
+			datatype) 
+			VALUES 
+			('". $this->Group->getID() ."',
+			'". htmlspecialchars($name) ."',
+			'". htmlspecialchars($description) ."',
+			'$is_public',
+			'$allow_anon',
+			'$email_all',
+			'$email_address',
+			'". ($due_period*(60*60*24)) ."',
+			'1209600',
+			'".htmlspecialchars($submit_instructions)."',
+			'".htmlspecialchars($browse_instructions)."',
+			'$datatype')";
+		
+		db_begin();
+		
+		$res = db_query($sql);
+
+		$id = db_insertid($res,'artifact_group_list','group_artifact_id');
+		
+		if (!$res || !$id) {
+			$this->setError('ArtifactType: '.db_error());
+			db_rollback();
+			return false;
+		} else {
+			if (!$this->fetchData($id)) {
+				db_rollback();
+				return false;
+			} else {
+				if (!$this->addAllUsers()) {
+					db_rollback();
+					return false;
+				} else {
+					db_commit();
+					return $id;
+				}
+			}
+		}
+	}
+
+	/**
+	 *  fetchData - re-fetch the data for this ArtifactType from the database.
+	 *
+	 *  @param	int		The artifact type ID.
+	 *  @return boolean	success.
+	 */
+	function fetchData($artifact_type_id) {
+		$res=db_query("SELECT * FROM artifact_group_list_vw
+			WHERE group_artifact_id='$artifact_type_id' 
+			AND group_id='". $this->Group->getID() ."'");
+		if (!$res || db_numrows($res) < 1) {
+			$this->setError('ArtifactType: Invalid ArtifactTypeID');
+			return false;
+		}
+		$this->data_array =& db_fetch_array($res);
+		db_free_result($res);
+		return true;
+	}
+
+	/**
+	 *	  getGroup - get the Group object this ArtifactType is associated with.
+	 *
+	 *	  @return	Object	The Group object.
+	 */
+	function &getGroup() {
+		return $this->Group;
+	}
+
+	/**
+	 *	  getID - get this ArtifactTypeID.
+	 *
+	 *	  @return	int	The group_artifact_id #.
+	 */
+	function getID() {
+		return $this->data_array['group_artifact_id'];
+	}
+
+	/**
+	 *	  getOpenCount - get the count of open tracker items in this tracker type.
+	 *
+	 *	  @return	int	The count.
+	 */
+	function getOpenCount() {
+		return $this->data_array['open_count'];
+	}
+
+	/**
+	 *	  getTotalCount - get the total number of tracker items in this tracker type.
+	 *
+	 *	  @return	int	The total count.
+	 */
+	function getTotalCount() {
+		return $this->data_array['count'];
+	}
+
+	/**
+	 *	  allowsAnon - determine if non-logged-in users can post.
+	 *
+	 *	  @return	boolean	allow_anonymous_submissions.
+	 */
+	function allowsAnon() {
+		return $this->data_array['allow_anon'];
+	}
+
+	/**
+	 *	  getSubmitInstructions - get the free-form string strings.
+	 *
+	 *	  @return	string	instructions.
+	 */
+	function getSubmitInstructions() {
+		return $this->data_array['submit_instructions'];
+	}
+
+	/**
+	 *	  getBrowseInstructions - get the free-form string strings.
+	 *
+	 *	  @return string instructions.
+	 */
+	function getBrowseInstructions() {
+		return $this->data_array['browse_instructions'];
+	}
+
+	/**
+	 *	  emailAll - determine if we're supposed to email on every event.
+	 *
+	 *	  @return	boolean	email_all.
+	 */
+	function emailAll() {
+		return $this->data_array['email_all_updates'];
+	}
+
+	/**
+	 *	  emailAddress - defined email address to send events to.
+	 *
+	 *	  @return	string	email.
+	 */
+	function getEmailAddress() {
+		return $this->data_array['email_address'];
+	}
+
+	/**
+	 *	  isPublic - whether non-group-members can view.
+	 *
+	 *	  @return boolean	is_public.
+	 */
+	function isPublic() {
+		return $this->data_array['is_public'];
+	}
+
+	/**
+	 *	  getName - the name of this ArtifactType.
+	 *
+	 *	  @return	string	name.
+	 */
+	function getName() {
+		return $this->data_array['name'];
+	}
+	
+	/**
+	 * getFormattedName - formatted name of this ArtifactType
+	 *
+	 * @return string formatted name
+	 */
+	function getFormattedName() {
+		$name = preg_replace('/[^[:alnum:]]/','',$this->getName());
+		$name = strtolower($name);
+		return $name;
+	}
+	
+	/**
+	 * getUnixName - returns the name used by email gateway
+	 *
+	 * @return string unix name
+	 */
+	function getUnixName() {
+		return strtolower($this->Group->getUnixName()).'-'.$this->getFormattedName();
+	}
+	
+	/**
+	 * getReturnEmailAddress() - return the return email address for notification emails
+	 *
+	 * @return string return email address
+	 */
+	function getReturnEmailAddress() {
+		global $sys_default_domain,$sys_use_gateways;
+		$address = '';
+		if($sys_use_gateways) {
+			$address .= strtolower($this->getUnixName());
+		} else {
+			$address .= 'noreply';
+		}
+		$address .= '@'.$sys_default_domain;
+		return $address;
+	}
+
+	/**
+	 *	  getDescription - the description of this ArtifactType.
+	 *
+	 *	  @return	string	description.
+	 */
+	function getDescription() {
+		return $this->data_array['description'];
+	}
+
+	/**
+	 *	  getDuePeriod - how many seconds until it's considered overdue.
+	 *
+	 *	  @return int seconds.
+	 */
+	function getDuePeriod() {
+		return $this->data_array['due_period'];
+	}
+
+	/**
+	 *	getStatusTimeout - how many seconds until an item is stale.
+	 *
+	 *	@return int seconds.
+	 */
+	function getStatusTimeout() {
+		return $this->data_array['status_timeout'];
+	}
+
+	/**
+	 *	  getCustomStatusField - return the extra_field_id of the field containing the custom status.
+	 *
+	 *	  @return	int	extra_field_id.
+	 */
+	function getCustomStatusField() {
+		return $this->data_array['custom_status_field'];
+	}
+
+	/**
+	 *	setCustomStatusField - set the extra_field_id of the field containing the custom status.
+	 *	@param	int	The extra field id.
+	 *	@return	boolean	success.
+	 */
+	function setCustomStatusField($extra_field_id) {
+		$res=db_query("UPDATE artifact_group_list SET custom_status_field='$extra_field_id'
+			WHERE group_artifact_id='".$this->getID()."'");
+		return $res;
+	}
+
+	/**
+	 *	  usesCustomStatuses - boolean
+	 *
+	 *	  @return	boolean	use_custom_statues.
+	 */
+	function usesCustomStatuses() {
+		return $this->getCustomStatusField();
+	}
+
+	/**
+	 *	remap status	- pass the extra_fields array and return the status_id, either open/closed
+	 *	@param	int	The status_id
+	 *	@param	array	Complex array of extra_field_data
+	 *	@return	int	status_id.
+	 */
+	function remapStatus($status_id,$extra_fields) {
+		if ($this->usesCustomStatuses()) {
+			//get the selected element for the extra_field_status element
+			$csfield = $this->getCustomStatusField();
+			if (array_key_exists($csfield, $extra_fields)) {
+				$element_id=$extra_fields[$csfield];
+
+				//convert that element_id into the status_id
+				$res=db_query("SELECT status_id FROM artifact_extra_field_elements WHERE element_id='$element_id'");
+				if (!$res) {
+					$this->setError('Error Remapping Status: '.db_error());
+					return false;
+				}
+				$status_id=db_result($res,0,'status_id');
+			} else {
+				// custom status was not passed... use the first status from the database
+				$res = db_query("SELECT status_id FROM artifact_extra_field_elements WHERE extra_field_id='".$csfield."' ORDER BY element_id ASC LIMIT 1 OFFSET 0");
+				if (db_numrows($res) == 0) {		// No values available
+					$this->setError('Error Remapping Status');
+					return false;
+				}
+				$status_id=db_result($res,0,'status_id');
+			}
+			
+			if ($status_id < 1 || $status_id > 4) {
+				echo "INVALID STATUS REMAP: $status_id FROM SELECTED ELEMENT: $element_id";
+				return false;
+			}
+			return $status_id;
+		} else {
+			return $status_id;
+		}
+	}
+
+	/**
+	 *	  getDataType - flag that is generally unused but can mark the difference between bugs, patches, etc.
+	 *
+	 *	  @return	int	The type (1) bug (2) support (3) patch (4) feature (0) other.
+	 */
+	function getDataType() {
+		return $this->data_array['datatype'];
+	}
+
+	/**
+	 *  setMonitor - user can monitor this artifact.
+	 *
+	 *  @return false - always false - always use the getErrorMessage() for feedback
+	 */
+	function setMonitor() {
+		global $Language;
+		if (session_loggedin()) {
+
+			$user_id=user_getid();
+			$user =& user_get_object(user_getid());
+
+		} else {
+
+			$this->setError($Language->getText('tracker_artifact','error_valid_email_required'));
+			return false;
+
+		}
+
+		$res=db_query("SELECT * FROM artifact_type_monitor
+			WHERE group_artifact_id='". $this->getID() ."'
+			AND user_id='$user_id'");
+
+		if (!$res || db_numrows($res) < 1) {
+			//not yet monitoring
+			$res=db_query("INSERT INTO artifact_type_monitor (group_artifact_id,user_id)
+				VALUES ('". $this->getID() ."','$user_id')");
+			if (!$res) {
+				$this->setError(db_error());
+				return false;
+			} else {
+				$this->setError($Language->getText('tracker_artifacttype','monitoring_activated'));
+				return false;
+			}
+		} else {
+			//already monitoring - remove their monitor
+			db_query("DELETE FROM artifact_type_monitor
+				WHERE group_artifact_id='". $this->getID() ."'
+				AND user_id='$user_id'");
+			$this->setError($Language->getText('tracker_artifacttype','monitoring_deactivated'));
+			return false;
+		}
+	}
+
+	function isMonitoring() {
+		if (!session_loggedin()) {
+			return false;
+		}
+		$sql="SELECT count(*) FROM artifact_type_monitor 
+			WHERE user_id='".user_getid()."' AND group_artifact_id='".$this->getID()."';";
+		$result = db_query($sql);
+		$row_count = db_fetch_array($result);
+		return $result && $row_count['count'] > 0;
+	}
+
+	/**
+	 *  getMonitorIds - array of email addresses monitoring this Artifact.
+	 *
+	 *  @return array of email addresses monitoring this Artifact.
+	 */
+	function &getMonitorIds() {
+		$res=db_query("SELECT user_id
+			FROM artifact_type_monitor
+			WHERE group_artifact_id='". $this->getID() ."'");
+		return util_result_column_to_array($res);
+	}
+
+	/**
+	 *	getExtraFields - List of possible user built extra fields
+	 *	set up for this artifact type.
+	 *
+	 *	@return arrays of data;
+	 */
+	function getExtraFields($filter='') {
+		if (!isset($this->extra_fields["$filter"])) {
+			$this->extra_fields["$filter"] = array();
+			if ($filter) {
+				$filter_str=" AND field_type IN ($filter) ";
+			} else {
+				$filter_str="";
+			}
+			$sql="select *
+				FROM artifact_extra_field_list 
+				WHERE group_artifact_id='".$this->getID() ."'
+				$filter_str
+				ORDER BY field_type ASC";
+			$res=db_query($sql);
+			while($arr = db_fetch_array($res)) {
+				$this->extra_fields["$filter"][$arr['extra_field_id']] = $arr;
+			}
+		}
+			
+		return $this->extra_fields["$filter"];
+	}
+
+	/**
+	 *	cloneFieldsFrom - clone all the fields and elements from another tracker
+	 *
+	 *	@return	boolean	true/false on success
+	 */
+	function cloneFieldsFrom($clone_tracker_id) {
+		global $sys_template_group;
+		$g =& group_get_object($sys_template_group);
+		if (!$g || !is_object($g)) {
+			$this->setError('Could Not Get Template Group');
+			return false;
+		} elseif ($g->isError()) {
+			$this->setError('Template Group Error '.$g->getErrorMessage());
+			return false;
+		}
+		$at =& new ArtifactType($g,$clone_tracker_id);
+		if (!$at || !is_object($at)) {
+			$this->setError('Could Not Get Tracker To Clone');
+			return false;
+		} elseif ($at->isError()) {
+			$this->setError('Clone Tracker Error '.$at->getErrorMessage());
+			return false;
+		}
+		$efs =& $at->getExtraFields();
+
+
+		//
+		//	Iterate list of extra fields
+		//
+		db_begin();
+		foreach ($efs as $ef) {
+			//new field in this tracker
+			$nef = new ArtifactExtraField($this);
+			if (!$nef->create( addslashes(util_unconvert_htmlspecialchars($ef['field_name'])), $ef['field_type'], $ef['attribute1'], $ef['attribute2'], $ef['is_required'], $ef['alias'])) {
+				db_rollback();
+				$this->setError('Error Creating New Extra Field: '.$nef->getErrorMessage());
+				return false;
+			}
+			//
+			//	Iterate the elements
+			//
+			$resel=db_query("SELECT * FROM artifact_extra_field_elements WHERE extra_field_id='".$ef['extra_field_id']."'");
+			while ($el =& db_fetch_array($resel)) {
+				//new element
+				$nel = new ArtifactExtraFieldElement($nef);
+				if (!$nel->create( addslashes(util_unconvert_htmlspecialchars($el['element_name'])), $el['status_id'] )) {
+					db_rollback();
+					$this->setError('Error Creating New Extra Field Element: '.$nel->getErrorMessage());
+					return false;
+				}
+			}
+		}
+		db_commit();
+		return true;
+
+	}
+
+	/**
+	 *	getExtraFieldName - Get a box name using the box ID 
+	 *
+	 *	@param  int 	id of an extra field.
+	 *	@return string	name of extra field.
+	 */
+	function getExtraFieldName($extra_field_id) {
+		$arr = $this->getExtraFields();
+		return $arr[$extra_field_id]['field_name'];
+	}
+
+	/**
+	 *	getExtraFieldElements - List of possible admin configured 
+	 *	extra field elements. This function is used to 
+	 *	present the boxes and choices on the main Add/Update page.
+	 *
+	 *	@param	int	id of the extra field
+	 *	@return array of elements for this extra field.
+	 */
+	function getExtraFieldElements($id) {
+//TODO validate $id
+		if (!$id) {
+			return false;
+		}
+		if (!isset($this->extra_field[$id])) {
+			$this->extra_field[$id] = array();
+			$sql="select element_id,element_name,status_id
+				FROM artifact_extra_field_elements
+				WHERE extra_field_id ='".$id."'  
+				ORDER BY element_id ASC";
+
+			$res=db_query($sql);
+			$i=0;
+			while($arr =& db_fetch_array($res)) {
+				$this->extra_field[$id][$i++] = $arr;
+			}
+//			if (count($this->extra_field[$id]) == 0) {
+//				return;
+//			}
+		}
+				
+		return $this->extra_field[$id];
+	}
+
+	/**
+	 *	getElementName - get the name of a particular element.
+	 *
+	 *	@return	string	The name.
+	 */
+	function getElementName($choiceid) {
+		if (!$choiceid) {
+			return '';
+		}
+		if (is_array($choiceid)) {
+			$choiceid=implode(',',$choiceid);
+		}
+		if ($choiceid == 100) {
+			return 'None';
+		}
+		if (!$this->element_name["$choiceid"]) {
+			$sql="select element_id,extra_field_id,element_name
+				FROM artifact_extra_field_elements
+				WHERE element_id IN ($choiceid)";
+			$res=db_query($sql);
+			if (db_numrows($res) > 1) {
+				$arr=util_result_column_to_array($res,2);
+				$this->element_name["$choiceid"]=implode(',',$arr);
+			} else {
+				$this->element_name["$choiceid"]=db_result($res,0,'element_name');
+			}
+		}
+		return $this->element_name["$choiceid"];
+	}
+
+	/**
+	 *	getElementStatusID - get the status of a particular element.
+	 *
+	 *	@return	int		The status
+	 */
+	function getElementStatusID($choiceid) {
+		if (!$choiceid) {
+			return 0;
+		}
+		if (is_array($choiceid)) {
+			$choiceid=implode(',',$choiceid);
+		}
+		if ($choiceid == 100) {
+			return 0;
+		}
+		if (!$this->element_status["$choiceid"]) {
+			$sql="select element_id,extra_field_id,status_id
+				FROM artifact_extra_field_elements
+				WHERE element_id IN ($choiceid)";
+			$res=db_query($sql);
+			if (db_numrows($res) > 1) {
+				$arr=util_result_column_to_array($res,2);
+				$this->element_status["$choiceid"]=implode(',',$arr);
+			} else {
+				$this->element_status["$choiceid"]=db_result($res,0,'status_id');
+			}
+		}
+		return $this->element_status["$choiceid"];
+	}
+
+
+	/**
+	 *	delete - delete this tracker and all its related data.
+	 *
+	 *	@param	bool	I'm Sure.
+	 *	@param	bool	I'm REALLY sure.
+	 *	@return	bool true/false;
+	 */
+	function delete($sure, $really_sure) {
+		if (!$sure || !$really_sure) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		db_begin();
+		db_query("DELETE FROM artifact_extra_field_data
+			WHERE EXISTS (SELECT artifact_id FROM artifact 
+			WHERE group_artifact_id='".$this->getID()."'
+			AND artifact.artifact_id=artifact_extra_field_data.artifact_id)");
+//echo '0.1'.db_error();
+		db_query("DELETE FROM artifact_extra_field_elements
+			WHERE EXISTS (SELECT extra_field_id FROM artifact_extra_field_list 
+			WHERE group_artifact_id='".$this->getID()."'
+			AND artifact_extra_field_list.extra_field_id = artifact_extra_field_elements.extra_field_id)");
+//echo '0.2'.db_error();
+		db_query ("DELETE FROM artifact_extra_field_list
+			WHERE group_artifact_id='".$this->getID()."'");
+//echo '0.3'.db_error();
+		db_query("DELETE FROM artifact_canned_responses 
+			WHERE group_artifact_id='".$this->getID()."'");
+//echo '1'.db_error();
+		db_query("DELETE FROM artifact_perm
+			WHERE group_artifact_id='".$this->getID()."'");
+//echo '3'.db_error();
+		db_query("DELETE FROM artifact_counts_agg
+			WHERE group_artifact_id='".$this->getID()."'");
+//echo '5'.db_error();
+		db_query("DELETE FROM artifact_file
+			WHERE EXISTS (SELECT artifact_id FROM artifact 
+			WHERE group_artifact_id='".$this->getID()."'
+			AND artifact.artifact_id=artifact_file.artifact_id)");
+//echo '6'.db_error();
+		db_query("DELETE FROM artifact_message
+			WHERE EXISTS (SELECT artifact_id FROM artifact 
+			WHERE group_artifact_id='".$this->getID()."'
+			AND artifact.artifact_id=artifact_message.artifact_id)");
+//echo '7'.db_error();
+		db_query("DELETE FROM artifact_history
+			WHERE EXISTS (SELECT artifact_id FROM artifact 
+			WHERE group_artifact_id='".$this->getID()."'
+			AND artifact.artifact_id=artifact_history.artifact_id)");
+//echo '8'.db_error();
+		db_query("DELETE FROM artifact_monitor
+			WHERE EXISTS (SELECT artifact_id FROM artifact 
+			WHERE group_artifact_id='".$this->getID()."'
+			AND artifact.artifact_id=artifact_monitor.artifact_id)");
+//echo '9'.db_error();
+		db_query("DELETE FROM artifact
+			WHERE group_artifact_id='".$this->getID()."'");
+//echo '4'.db_error();
+		db_query("DELETE FROM artifact_group_list
+			WHERE group_artifact_id='".$this->getID()."'");
+//echo '11'.db_error();
+		
+		db_commit();
+		return true;
+	}
+
+	/**
+	 *	getTechnicians - returns a result set of technicians.
+	 *
+	 *	@return database result set.
+	 */
+	function getTechnicians() {
+		if (!isset($this->technicians_res)) {
+			$sql="SELECT user_id,realname 
+				FROM artifactperm_user_vw
+				WHERE group_artifact_id='". $this->getID() ."' 
+				AND perm_level in (1,2)
+				ORDER BY realname";
+			$this->technicians_res = db_query($sql);
+		}
+		return $this->technicians_res;
+	}
+
+	/**
+	 *	getTechnicianObjects - Array of User objects set up for this artifact type.
+	 *
+	 *	@return	array	Of User objects.
+	 */
+	function &getTechnicianObjects() {
+		$res = $this->getTechnicians();
+		$arr =& util_result_column_to_array($res,0);
+		return user_get_objects($arr);
+	}
+
+	/**
+	 *	getCannedResponses - returns a result set of canned responses.
+	 *
+	 *	@return database result set.
+	 */
+	function getCannedResponses() {
+		if (!isset($this->cannedresponses_res)) {
+			$sql="SELECT id,title
+				FROM artifact_canned_responses 
+				WHERE group_artifact_id='". $this->getID() ."'";
+			$this->cannedresponses_res = db_query($sql);
+		}
+		return $this->cannedresponses_res;
+	}
+
+	/**
+	 *	getStatuses - returns a result set of statuses.
+	 *
+	 *	These statuses are either the default open/closed or any number of 
+	 *	custom statuses that are stored in the extra fields. On insert/update
+	 *	to an artifact the status_id is remapped from the extra_field_element_id to
+	 *	the standard open/closed id.
+	 *
+	 *	@param	boolean	Whether to show the real statuses or not.
+	 *	@return database result set.
+	 */
+	function getStatuses() {
+		if (!isset($this->status_res)) {
+			$sql="select * from artifact_status";
+			$this->status_res=db_query($sql);
+		}
+		return $this->status_res;
+	}
+
+	/**
+	 * getStatusName - returns the name of this status.
+	 *
+	 * @param	int		The status ID.
+	 * @return	string	name.
+	 */
+	function getStatusName($id) {
+		$sql="select status_name from artifact_status WHERE id='$id'";
+		$result=db_query($sql);
+		if ($result && db_numrows($result) > 0) {
+			return db_result($result,0,'status_name');
+		} else {
+			return 'Error - Not Found';
+		}
+	}
+
+	/**
+	 *  addAllUsers - add all users to this artifact.
+	 *
+	 *  @return boolean success.
+	 */
+	function addAllUsers() {
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		$sql="INSERT INTO artifact_perm (group_artifact_id,user_id,perm_level)
+			SELECT '".$this->getID()."',user_id,artifact_flags
+			FROM user_group
+			WHERE
+			group_id='".$this->Group->getID()."'
+			AND NOT EXISTS (SELECT user_id FROM artifact_perm
+			WHERE group_artifact_id='".$this->getID()."'
+			AND user_id=user_group.user_id);";
+		$res= db_query($sql);
+		if (!$res) {
+			$this->setError(db_error());
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 *	addUser - add a user to this ArtifactType.
+	 *
+	 *	@param	int		user_id of the new user.
+	 *	@return boolean	success.
+	 */
+	function addUser($id) {
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		if (!$id) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		$sql="SELECT * FROM artifact_perm 
+			WHERE group_artifact_id='".$this->getID()."' 
+			AND user_id='$id'";
+		$result=db_query($sql);
+		if (db_numrows($result) > 0) {
+			return true;
+		} else {
+			$sql="INSERT INTO artifact_perm (group_artifact_id,user_id,perm_level) 
+				VALUES ('".$this->getID()."','$id',0)";
+			$result=db_query($sql);
+			if ($result && db_affected_rows($result) > 0) {
+				return true;
+			} else {
+				$this->setError(db_error());
+				return false;
+			}
+		}
+	}
+
+	/**
+	 *	updateUser - update a user's permissions.
+	 *
+	 *	@param	int		user_id of the user to update.
+	 *	@param	int		(0) read only, (1) tech only, (2) admin & tech (3) admin only.
+	 *	@return boolean	success.
+	 */
+	function updateUser($id,$perm_level) {
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		if (!$id) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		$sql="UPDATE artifact_perm SET perm_level='$perm_level'
+			WHERE user_id='$id' AND group_artifact_id='".$this->getID()."'";
+		$result=db_query($sql);
+		if (db_affected_rows($result) < 1) {
+			//
+			//  If not, insert it.
+			//
+			$sql="INSERT INTO artifact_perm (group_artifact_id,user_id,perm_level) VALUES
+				('".$this->getID()."','$id','$perm_level')";
+			$result=db_query($sql);
+			if (!$result) {
+				$this->setError(db_error());
+				return false;
+			} else {
+				return true;
+			}
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 *	deleteUser - delete a user's permissions.
+	 *
+	 *	@param	int		user_id of the user who's permissions to delete.
+	 *	@return boolean	success.
+	 */
+	function deleteUser($id) {
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+		if (!$id) {
+			$this->setMissingParamsError();
+			return false;
+		}
+		$sql="DELETE FROM artifact_perm
+			WHERE user_id='$id' AND group_artifact_id='".$this->getID()."'";
+		$result=db_query($sql);
+		if ($result) {
+			return true;
+		} else {
+			$this->setError(db_error());
+			return false;
+		}
+	}
+
+	/*
+
+		USER PERMISSION FUNCTIONS
+
+	*/
+
+	/**
+	 *	  userCanView - determine if the user can view this artifact type.
+	 *
+	 *	  @return boolean	user_can_view.
+	 */
+	function userCanView() {
+		if ($this->isPublic()) {
+			return true;
+		} else {
+			if (!session_loggedin()) {
+				return false;
+			} else {
+				//
+				//	You must have an entry in artifact_perm if this tracker is not public
+				//
+				if ($this->userIsAdmin() || $this->getCurrentUserPerm() >= 0) {
+					return true;
+				} else {
+					return false;
+				}
+			}
+		}
+	}
+
+	/**
+	 *	userIsAdmin - see if the logged-in user's perms are >= 2 or Group ArtifactAdmin.
+	 *
+	 *	@return boolean	user_is_admin.
+	 */
+	function userIsAdmin() { 
+		if (!session_loggedin()) {
+			return false;
+		} else {
+			$perm =& $this->Group->getPermission( session_get_user() );
+
+			if (($this->getCurrentUserPerm() >= 2) || ($perm->isArtifactAdmin())) {
+				return true;
+			} else {
+				return false;
+			}
+		}
+	}
+
+	/**
+	 *	userIsTechnician - see if the logged-in user's perms are >= 1 or Group ArtifactAdmin.
+	 *
+	 *	@return boolean	user_is_technician.
+	 */
+	function userIsTechnician() { 
+		if (!session_loggedin()) {
+			return false;
+		} else {
+			$perm =& $this->Group->getPermission( session_get_user() );
+
+			if (($this->getCurrentUserPerm() >= 1) || ($perm->isArtifactAdmin())) {
+				return true;
+			} else {
+				return false;
+			}
+		}
+	}
+
+	/**
+	 *	getCurrentUserPerm - get the logged-in user's perms from artifact_perm.
+	 *
+	 *	@return int perm level for the logged-in user.
+	 */
+	function getCurrentUserPerm() {
+		if (!session_loggedin()) {
+			return 0;
+		} else {
+			if (!isset($this->current_user_perm)) {
+				$sql="select perm_level
+				FROM artifact_perm
+				WHERE group_artifact_id='". $this->getID() ."'
+				AND user_id='".user_getid()."'";
+				$this->current_user_perm=db_result(db_query($sql),0,0);
+			}
+			return $this->current_user_perm;
+		}
+	}
+
+	/**
+	 *  update - use this to update this ArtifactType in the database.
+	 *
+	 *  @param	string	The item name.
+	 *  @param	string	The item description.
+	 *  @param	bool	(1) true (0) false - whether to email on all updates.
+	 *  @param	string	The address to send new entries and updates to.
+	 *  @param	int		Days before this item is considered overdue.
+	 *  @param	int		Days before stale items time out.
+	 *  @param	bool	(1) true (0) false - whether the resolution box should be shown.
+	 *  @param	string	Free-form string that project admins can place on the submit page.
+	 *  @param	string	Free-form string that project admins can place on the browse page.
+	 *  @return true on success, false on failure.
+	 */
+	function update($name,$description,$email_all,$email_address,
+		$due_period, $status_timeout,$use_resolution,$submit_instructions,$browse_instructions) {
+
+		global $Language;
+
+		if (!$this->userIsAdmin()) {
+			$this->setPermissionDeniedError();
+			return false;
+		}
+
+		if ($this->getDataType()) {
+			$name=$this->getName();
+			$description=$this->getDescription();
+		}
+		
+		if (!$name || !$description || !$due_period || !$status_timeout) {
+			$this->setError($Language->getText('tracker_artifacttype','required_fields'));
+			return false;
+		}
+		
+		if ($email_address) {
+			$invalid_emails = validate_emails($email_address);
+			if (count($invalid_emails) > 0) {
+				$this->SetError($Language->getText('tracker_artifacttype','invalid_emails').': '.implode(',',$invalid_emails));
+				return false;
+			}
+		}
+
+		$email_all = ((!$email_all) ? 0 : $email_all); 
+		$use_resolution = ((!$use_resolution) ? 0 : $use_resolution); 
+
+		$sql="UPDATE artifact_group_list SET 
+			name='". htmlspecialchars($name). "',
+			description='". htmlspecialchars($description) ."',
+			email_all_updates='$email_all',
+			email_address='$email_address',
+			due_period='". ($due_period * (60*60*24)) ."',
+			status_timeout='". ($status_timeout * (60*60*24)) . "',
+			submit_instructions='". htmlspecialchars($submit_instructions)."',
+			browse_instructions='" .htmlspecialchars($browse_instructions)."'
+			WHERE 
+			group_artifact_id='". $this->getID() ."' 
+			AND group_id='". $this->Group->getID() ."'";
+
+		$res=db_query($sql);
+		if (!$res || db_affected_rows($res) < 1) {
+			$this->setError('ArtifactType::Update(): '.db_error());
+			return false;
+		} else {
+			$this->fetchData($this->getID());
+			return true;
+		}
+	}
+
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/tracker/ArtifactTypeFactory.class
===================================================================
--- trunk/gforge_base/gforge/common/tracker/ArtifactTypeFactory.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/tracker/ArtifactTypeFactory.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,154 @@
+<?php
+/**
+ * GForge Tracker Facility
+ *
+ * Copyright 2002 GForge, LLC
+ * http://gforge.org/
+ *
+ * @version   $Id: ArtifactTypeFactory.class 5527 2006-06-05 20:10:10Z lo-lan-do $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
+ */
+
+require_once('common/include/Error.class');
+require_once('common/tracker/ArtifactType.class');
+
+class ArtifactTypeFactory extends Error {
+
+	/**
+	 * The Group object.
+	 *
+	 * @var	 object  $Group.
+	 */
+	var $Group;
+
+	/**
+	 * The ArtifactTypes array.
+	 *
+	 * @var	 array	ArtifactTypes.
+	 */
+	var $ArtifactTypes;
+
+	/**
+	 * The data type (DAO)
+	 *
+  	 * @var 	string dataType
+	 */
+	var $dataType;
+
+	/**
+	 *  Constructor.
+	 *
+	 *	@param	object	The Group object to which this ArtifactTypeFactory is associated
+	 *	@return	boolean	success.
+	 */
+	function ArtifactTypeFactory(&$Group) {
+		$this->Error();
+		if (!$Group || !is_object($Group)) {
+			$this->setError('ArtifactTypeFactory:: No Valid Group Object');
+			return false;
+		}
+		if ($Group->isError()) {
+			$this->setError('ArtifactTypeFactory:: '.$Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $Group;
+
+		return true;
+	}
+
+	/**
+	 *	getGroup - get the Group object this ArtifactType is associated with.
+	 *
+	 *	@return	object	The Group object.
+	 */
+	function &getGroup() {
+		return $this->Group;
+	}
+
+	/**
+	 *	getArtifactTypes - return an array of ArtifactType objects.
+	 *
+	 *	@return	array	The array of ArtifactType objects.
+	 */
+	function &getArtifactTypes() {
+		if ($this->ArtifactTypes) {
+			return $this->ArtifactTypes;
+		}
+		$permissions='';
+		if (session_loggedin()) {
+			$perm =& $this->Group->getPermission( session_get_user() );
+			if (!$perm || !is_object($perm) || !$perm->isMember()) {
+				$public_flag='=1';
+			} else {
+				$public_flag='<3';
+				if ($perm->isArtifactAdmin()) {
+					$exists='';
+				} else {
+					$exists=" AND group_artifact_ID IN (SELECT group_artifact_ID
+					FROM artifact_perm
+					WHERE perm_level >= 0 AND group_artifact_id=artifact_group_list.group_artifact_id
+					AND user_id='".user_getid()."') ";
+				}
+			}
+		} else {
+			$public_flag='=1';
+		}
+
+		$sql="SELECT * FROM artifact_group_list_vw
+			WHERE group_id='". $this->Group->getID() ."'
+			AND is_public $public_flag
+			$exists
+			ORDER BY group_artifact_id ASC";
+
+		$result = db_query ($sql);
+
+		$rows = db_numrows($result);
+
+		if (!$result || $rows < 1) {
+			$this->setError('None Found '.db_error());
+			return false;
+		} else {
+			while ($arr =& db_fetch_array($result)) {
+				$artifactType = new ArtifactType($this->Group, $arr['group_artifact_id'], $arr);
+				if($artifactType->isError()) {
+					$this->setError($artifactType->getErrorMessage());
+				} else {
+					$this->ArtifactTypes[] = $artifactType;
+				}
+			}
+		}
+		return $this->ArtifactTypes;
+	}
+
+	/**
+	 * getPublicFlag - a utility method to load up the current user's permissions
+ 	 *
+	 * @return 	string 	The public_flag field to plug into a SQL string
+	 */	
+	function &getPublicFlag() {
+		return $public_flag;
+	}
+
+}
+
+// Local Variables:
+// mode: php
+// c-file-style: "bsd"
+// End:
+
+?>

Added: trunk/gforge_base/gforge/common/tracker/ArtifactTypes.class
===================================================================
--- trunk/gforge_base/gforge/common/tracker/ArtifactTypes.class	                        (rev 0)
+++ trunk/gforge_base/gforge/common/tracker/ArtifactTypes.class	2008-02-13 13:04:48 UTC (rev 9)
@@ -0,0 +1,138 @@
+<?php
+/**
+ * ArtifactTypes.class - Class to handle artifact types
+ *
+ * Copyright 1999-2001 (c) VA Linux Systems
+ * The rest Copyright 2002-2004 (c) GForge Team
+ * http://gforge.org/
+ *
+ * @version   $Id: ArtifactTypes.class 5527 2006-06-05 20:10:10Z lo-lan-do $
+ *
+ * This file is part of GForge.
+ *
+ * GForge 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.
+ *
+ * GForge 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 GForge; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+require_once('common/include/Error.class');
+require_once('common/tracker/ArtifactType.class');
+require_once('common/tracker/ArtifactExtraField.class');
+
+class ArtifactTypes extends Error {
+
+	/** 
+	 * The artifact type object.
+	 *
+	 * @var		object	$ArtifactType.
+	 */
+	var $Group; //group object
+
+	/**
+	 * Array of artifactTypes data.
+	 *
+	 * @var		array	$data_array.
+	 */
+	var $data_array;
+
+	/**
+	 *	ArtifactTypes - constructor.
+	 *
+	 *	@param	object	The Group object.
+	 *	@return	boolean	success.
+	 */
+	function ArtifactTypes(&$Group) {
+		$this->Error();
+		if (!$Group || !is_object($Group)) {
+			$this->setError('No Valid Group Object');
+			return false;
+		}
+		if ($Group->isError()) {
+			$this->setError('ArtifactType: '.$Group->getErrorMessage());
+			return false;
+		}
+		$this->Group =& $Group;
+		return true;
+	}
+
+	/**
+	 *	createTrackers - creates all the standard trackers for a given Group.
+	 *
+	 *	@return	boolean	success.
+	 */
+	function createTrackers() {
+
+		// first, check if trackers already exist
+		$res=db_query("SELECT * FROM artifact_group_list 
+			WHERE group_id='".$this->Group->getID()."' AND datatype > 0");
+		if (db_numrows($res) > 0) {
+			return true;
+		}
+
+		include ('common/tracker/artifact_type_definitions.php');
+		db_begin();
+		foreach ($trackers as $trk) {
+			$at = new ArtifactType($this->Group);
+			if (!$at || !is_object($at)) {
+				db_rollback();
+				$this->setError('Error Getting Tracker Object');
+				return false;
+			}
+			//
+			//	Create a tracker
+			//
+			if (!$at->create(addslashes($trk[0]), addslashes($trk[1]), $trk[2], $trk[3], $trk[4], $trk[5], $trk[6], $trk[7], $trk[8], $trk[9], $trk[10])) {
+				db_rollback();
+				$this->setError('Error Creating Tracker: '.$at->getErrorMessage());
+				return false;
+			} else {
+				//
+				//	Create each field in the tracker
+				//
+				foreach ($trk[11] AS $fld) {
+					$aef = new ArtifactExtraField($at);
+//print($fld[0])."***|";
+					if (!$aef->create(addslashes($fld[0]), $fld[1], $fld[2], $fld[3], $fld[4])) {
+						db_rollback();
+						$this->setError('Error Creating Extra Field: '.$aef->get