<?php
/**************************************************************************
* This file is part of the WebIssues Server program
* Copyright (C) 2006 Michał Męciński
* Copyright (C) 2007-2009 WebIssues Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
**************************************************************************/

define( 'VERSION', '0.8.5' );

if( !include_once( 'config/config.inc.php' ) )
    die( '<p><strong>Fatal Error:</strong> The configuration file <tt>config/config.inc.php</tt> does not exist.</p>' );

require_once( 'include/database.inc.php' );
require_once( 'include/protocol.inc.php' );
require_once( 'include/validation.inc.php' );
require_once( 'include/templates.inc.php' );
require_once( 'include/mail.inc.php' );

define( 'ACCESS_NONE', 0 );
define( 'ACCESS_NORMAL', 1 );
define( 'ACCESS_ADMIN', 2 );

define( 'TIME_RESOLUTION', 5 * 60 );

main();

function main()
{
    global $config;
    global $server_name;
    global $current_time;
    global $last_time;

    $log = wi_log_open( 'cron' );

    if ( !wi_check_database() ) {
        trigger_error( 'Database engine is not available', E_USER_WARNING );
        if ( $log )
            fwrite( $log, "Cron task aborted: database engine is not available\n" );
        exit;
    }

    if ( !wi_open_database() ) {
        if ( $log )
            fwrite( $log, "Cron task aborted: cannot open database connection\n" );
        exit;
    }

    $query = 'SELECT server_name, db_version, last_cron FROM {server}';
    $row = wi_query_row( $query );
    $server_name = $row[ 'server_name' ];
    $db_version = $row[ 'db_version' ];
    $last_time = $row[ 'last_cron' ];

    if ( version_compare( $db_version, VERSION, '!=' ) ) {
        if ( $log )
            fwrite( $log, "Cron task aborted: invalid database version\n" );
        exit;
    }

    if ( $log )
        fwrite( $log, "Cron task started\n" );

    $result = true;

    $current_time = (int)( ( time() + TIME_RESOLUTION / 2 ) / TIME_RESOLUTION ) * TIME_RESOLUTION;
    if ( $last_time == null )
        $last_time = $current_time;

    if ( $config[ 'notify_enabled' ] ) {
        list( $count, $sent ) = wi_cron_send_notifications();
        if ( $sent < $count )
            $result = false;
        if ( $log && $count > 0 )
            fwrite( $log, "Sent $sent of $count notification(s)\n" );
    }

    $query = 'UPDATE {server} SET last_cron = %d';
    wi_query( $query, $current_time );

    if ( $log ) {
        if ( $result )
            fwrite( $log, "Cron task completed successfully\n" );
        else
            fwrite( $log, "Cron task completed with errors\n" );
    }
}

function wi_cron_send_notifications()
{
    global $config;
    global $current_time;

    $query = 'SELECT u.user_id, u.user_name, u.user_access, pm.pref_value AS email, pf.pref_value AS frequency,'
        . ' pz.pref_value AS timezone, nu.last_sent'
        . ' FROM {users} AS u'
        . ' JOIN {preferences} AS pm ON pm.user_id = u.user_id AND pm.pref_key = %s'
        . ' JOIN {preferences} AS pf ON pf.user_id = u.user_id AND pf.pref_key = %s'
        . ' LEFT OUTER JOIN {preferences} AS pz ON pz.user_id = u.user_id AND pz.pref_key = %s'
        . ' LEFT OUTER JOIN {notify_users} AS nu ON nu.user_id = u.user_id'
        . ' WHERE ( u.user_access > %d ) AND ( nu.last_sent IS NULL OR nu.last_sent <= %d )';

    $rs = wi_query( $query, 'email', 'notify-frequency', 'timezone', ACCESS_NONE, $current_time - $config[ 'notify_min_span' ] * 60 );

    $users = array();
    while ( $row = wi_fetch( $rs ) ) {
        $user_id = $row[ 'user_id' ];
        $users[ $user_id ]->name = $row[ 'user_name' ];
        $users[ $user_id ]->access = $row[ 'user_access' ];
        $users[ $user_id ]->email = $row[ 'email' ];
        $users[ $user_id ]->frequency = $row[ 'frequency' ];
        $users[ $user_id ]->timezone = $row[ 'timezone' ];
        $users[ $user_id ]->last_sent = $row[ 'last_sent' ];
    }

    $count = 0;
    $sent = 0;
    foreach ( $users as $user_id => $user ) {
        if ( wi_cron_check_frequency( $user->frequency, $user->timezone ) ) {
            $result = wi_cron_send_user_notification( $user_id, $user->name, $user->access, $user->email, $user->timezone );
            if ( $result !== null ) {
                if ( $user->last_sent === null )
                    $query = 'INSERT INTO {notify_users} ( user_id, last_sent, last_status ) VALUES ( %d, %d, %d )';
                else
                    $query = 'UPDATE {notify_users} SET last_sent = %2$d, last_status = %3$d WHERE user_id = %1$d';
                wi_query( $query, $user_id, $current_time, $result ? 0 : 1 );
                $count++;
                if ( $result )
                    $sent++;
            }
        }
    }

    return array( $count, $sent );
}

function wi_cron_check_frequency( $frequency, $timezone )
{
    global $current_time;
    global $last_time;

    $info = wi_parse_frequency( $frequency );

    if ( $info[ 'type' ] == 'IMMEDIATELY' )
        return true;

    $time = wi_metadata( $info, 'time', '00:00' );
    $seconds = wi_read_time( $time );

    if ( $timezone === null )
        $zone_offset = date( 'Z' );
    else
        $zone_offset = wi_read_timezone( $timezone );

    $utc_seconds = $seconds - $zone_offset;
    $last_daily = (int)( ( $current_time - $utc_seconds ) / 86400 ) * 86400 + $utc_seconds;

    if ( $info[ 'type' ] == 'DAILY' )
        return ( $last_time < $last_daily );

    $day = wi_metadata( $info, 'day', 0 );
    $last_day = (int)gmdate( 'w', $last_daily + $zone_offset );
    $last_weekly = $last_daily - ( ( $last_day < $day ) ? $last_day - $day + 7 : $last_day - $day ) * 86400;

    if ( $info[ 'type' ] = 'WEEKLY' )
        return ( $last_time < $last_weekly );

    return false;
}

function wi_cron_send_user_notification( $user_id, $user_name, $user_access, $user_email, $timezone )
{
    global $config;
    global $server_name;
    global $current_time;

    $projects = array();

    $folders = array();
    $issues = array();

    $last_issue = 0;
    $last_stamp = 0;

    $query = 'SELECT i.issue_id, i.issue_name, sc.stamp_time AS created_time, uc.user_name AS created_by,'
        . ' f.folder_id, f.folder_name, p.project_id, p.project_name'
        . ' FROM {issues} AS i'
        . ' JOIN {folders} AS f ON f.folder_id = i.folder_id'
        . ' JOIN {projects} AS p ON p.project_id = f.project_id'
        . ' JOIN {notify_folders} AS nf ON nf.folder_id = i.folder_id AND nf.user_id = %1$d'
        . ' JOIN {stamps} AS sc ON sc.stamp_id = i.issue_id'
        . ' JOIN {users} AS uc ON uc.user_id = sc.user_id';
    if ( $user_access < ACCESS_ADMIN )
        $query .= ' JOIN {rights} AS r ON r.project_id = f.project_id AND r.user_id = %1$d';
    $query .= ' WHERE i.issue_id > nf.stamp_id';

    $rs = wi_query( $query, $user_id );

    while ( $row = wi_fetch( $rs ) ) {
        $project_id = $row[ 'project_id' ];
        if ( !isset( $projects[ $project_id ] ) ) {
            $projects[ $project_id ]->name = $row[ 'project_name' ];
            $projects[ $project_id ]->folders = array();
        }
        $folder_id = $row[ 'folder_id' ];
        if ( !isset( $projects[ $project_id ]->folders[ $folder_id ] ) ) {
            $projects[ $project_id ]->folders[ $folder_id ]->name = $row[ 'folder_name' ];
            $projects[ $project_id ]->folders[ $folder_id ]->issues = array();
        }
        $issue_id = $row[ 'issue_id' ];
        $projects[ $project_id ]->folders[ $folder_id ]->issues[ $issue_id ]->name = $row[ 'issue_name' ];
        $projects[ $project_id ]->folders[ $folder_id ]->issues[ $issue_id ]->created_time = $row[ 'created_time' ];
        $projects[ $project_id ]->folders[ $folder_id ]->issues[ $issue_id ]->created_by = $row[ 'created_by' ];

        $folders[ $folder_id ] = true;

        if ( $last_issue < $issue_id )
            $last_issue = $issue_id;
    }

    $query = 'SELECT i.issue_id, i.stamp_id, i.issue_name, sm.stamp_time AS modified_time, um.user_name AS modified_by,'
        . ' f.folder_id, f.folder_name, p.project_id, p.project_name'
        . ' FROM {issues} AS i'
        . ' JOIN {folders} AS f ON f.folder_id = i.folder_id'
        . ' JOIN {projects} AS p ON p.project_id = f.project_id'
        . ' JOIN {notify_issues} AS ni ON ni.issue_id = i.issue_id AND ni.user_id = %1$d'
        . ' JOIN {stamps} AS sm ON sm.stamp_id = i.stamp_id'
        . ' JOIN {users} AS um ON um.user_id = sm.user_id';
    if ( $user_access < ACCESS_ADMIN )
        $query .= ' JOIN {rights} AS r ON r.project_id = f.project_id AND r.user_id = %1$d';
    $query .= ' WHERE i.stamp_id > ni.stamp_id';

    $rs = wi_query( $query, $user_id );

    while ( $row = wi_fetch( $rs ) ) {
        $project_id = $row[ 'project_id' ];
        if ( !isset( $projects[ $project_id ] ) ) {
            $projects[ $project_id ]->name = $row[ 'project_name' ];
            $projects[ $project_id ]->folders = array();
        }
        $folder_id = $row[ 'folder_id' ];
        if ( !isset( $projects[ $project_id ]->folders[ $folder_id ] ) ) {
            $projects[ $project_id ]->folders[ $folder_id ]->name = $row[ 'folder_name' ];
            $projects[ $project_id ]->folders[ $folder_id ]->issues = array();
        }
        $issue_id = $row[ 'issue_id' ];
        $stamp_id = $row[ 'stamp_id' ];
        $projects[ $project_id ]->folders[ $folder_id ]->issues[ $issue_id ]->name = $row[ 'issue_name' ];
        $projects[ $project_id ]->folders[ $folder_id ]->issues[ $issue_id ]->modified_time = $row[ 'modified_time' ];
        $projects[ $project_id ]->folders[ $folder_id ]->issues[ $issue_id ]->modified_by = $row[ 'modified_by' ];

        $issues[ $issue_id ] = true;

        if ( $last_stamp < $stamp_id )
            $last_stamp = $stamp_id;
    }

    if ( empty( $projects ) )
        return null;

    $count_projects = 0;
    $count_folders = 0;
    $count_new = 0;
    $count_modified = 0;
    uasort( $projects, 'wi_cron_sort_by_name' );
    foreach ( $projects as $project_id => $project ) {
        $project->count_folders = 0;
        $project->count_new = 0;
        $project->count_modified = 0;
        uasort( $project->folders, 'wi_cron_sort_by_name' );
        foreach ( $project->folders as $folder_id => $folder ) {
            $folder->count_new = 0;
            $folder->count_modified = 0;
            ksort( $folder->issues );
            foreach ( $folder->issues as $issue_id => $issue ) {
                if ( isset( $issue->created_time ) ) {
                    $count_new++;
                    $project->count_new++;
                    $folder->count_new++;
                } else {
                    $count_modified++;
                    $project->count_modified++;
                    $folder->count_modified++;
                }
            }
            $count_folders++;
            $project->count_folders++;
        }
        $count_projects++;
    }

    $template = wi_load_template( $config[ 'notify_template' ] );

    if ( $template === false )
        return false;

    $args = array();
    $args[ 'server_name' ] = $server_name;
    $args[ 'user_id' ] = $user_id;
    $args[ 'user_name' ] = $user_name;
    $args[ 'user_email' ] = $user_email;
    $args[ 'count_projects' ] = $count_projects;
    $args[ 'count_folders' ] = $count_folders;
    $args[ 'count_new' ] = $count_new;
    $args[ 'count_modified' ] = $count_modified;

    $template_headers = wi_get_template( $template, 'headers', $args );

    $headers = wi_parse_headers( $template_headers );

    $subject = wi_header( $headers, 'Subject' );
    $content_type = wi_header( $headers, 'Content-Type', 'text/plain' );

    $is_html = ( $content_type == 'text/html' );
    if ( $is_html ) {
        $args[ 'server_name' ] = htmlspecialchars( $server_name );
        $args[ 'user_name' ] = htmlspecialchars( $user_name );
    }

    $body_start = wi_get_template( $template, 'body_start', $args );
    $body_end = wi_get_template( $template, 'body_end', $args );

    if ( $timezone === null )
        $zone_offset = date( 'Z' );
    else
        $zone_offset = wi_read_timezone( $timezone );

    $body_projects = '';
    foreach ( $projects as $project_id => $project ) {
        $args[ 'project_id' ] = $project_id;
        $args[ 'project_name' ] = $is_html ? htmlspecialchars( $project->name ) : $project->name;
        $args[ 'count_folders' ] = $project->count_folders;
        $args[ 'count_new' ] = $project->count_new;
        $args[ 'count_modified' ] = $project->count_modified;
        $project_start = wi_get_template( $template, 'project_start', $args );
        $project_end = wi_get_template( $template, 'project_end', $args );
        $project_folders = '';
        foreach ( $project->folders as $folder_id => $folder ) {
            $args[ 'folder_id' ] = $folder_id;
            $args[ 'folder_name' ] = $is_html ? htmlspecialchars( $folder->name ) : $folder->name;
            $args[ 'count_new' ] = $folder->count_new;
            $args[ 'count_modified' ] = $folder->count_modified;
            $folder_start = wi_get_template( $template, 'folder_start', $args );
            $folder_end = wi_get_template( $template, 'folder_end', $args );
            $folder_issues = '';
            foreach ( $folder->issues as $issue_id => $issue ) {
                $args[ 'issue_id' ] = $issue_id;
                $args[ 'issue_name' ] = $is_html ? htmlspecialchars( $issue->name ) : $issue->name;
                if ( isset( $issue->created_time ) ) {
                    $args[ 'created_time' ] = gmdate( 'Y-m-d H:i', $issue->created_time + $zone_offset );
                    $args[ 'created_by' ] = $is_html ? htmlspecialchars( $issue->created_by ) : $issue->created_by;
                    $folder_issues .= wi_get_template( $template, 'issue_created', $args );
                } else {
                    $args[ 'modified_time' ] = gmdate( 'Y-m-d H:i', $issue->modified_time + $zone_offset );
                    $args[ 'modified_by' ] = $is_html ? htmlspecialchars( $issue->modified_by ) : $issue->modified_by;
                    $folder_issues .= wi_get_template( $template, 'issue_modified', $args );
                }
            }
            $project_folders .= $folder_start . $folder_issues . $folder_end;
        }
        $body_projects .= $project_start . $project_folders . $project_end;
    }

    $body = $body_start . $body_projects . $body_end;

    if ( $body == '' )
        return false;

    if ( !wi_mail( $user_email, $subject, $body, $content_type ) )
        return false;

    if ( !empty( $folders ) ) {
        $query = 'UPDATE {notify_folders} SET stamp_id = %d WHERE folder_id IN ( %l ) AND user_id = %d';
        wi_query( $query, $last_issue, array_keys( $folders ), $user_id );
    }

    if ( !empty( $issues ) ) {
        $query = 'UPDATE {notify_issues} SET stamp_id = %d WHERE issue_id IN ( %l ) AND user_id = %d';
        wi_query( $query, $last_stamp, array_keys( $issues ), $user_id );
    }

    return true;
}

function wi_cron_sort_by_name( $a, $b )
{
    return strcasecmp( $a->name, $b->name );
}
