Location: PHPKode > scripts > WebCalendar > WebCalendar-1.2.4/includes/functions.php
<?php
/* Most of WebCalendar's functions.
 *
 * @author Craig Knudsen <hide@address.com>
 * @copyright Craig Knudsen, <hide@address.com>, http://www.k5n.us/cknudsen
 * @license http://www.gnu.org/licenses/gpl.html GNU GPL
 * @version $Id: functions.php,v 1.520.2.49 2011/06/02 12:44:30 rjones6061 Exp $
 * @package WebCalendar
 */

/* Functions start here. All non-function code should be above this.
 *
 * Note to developers:
 *  Documentation is generated from the function comments below.
 *  When adding/updating functions, please follow these conventions.
 *  Your cooperation in this matter is appreciated. :-)
 *
 *  If you want your documentation to link to the db documentation,
 *  just make sure you mention the db table name followed by "table"
 *  on the same line. Here's an example:
 *    Retrieve preferences from the webcal_user_pref table.
 */

/* Logs a debug message.
 *
 * Generally, we try not to leave calls to this function in the code.
 * It is used for debugging only.
 *
 * @param string $msg Text to be logged
 */
function do_debug ( $msg ) {
  // log to /tmp/webcal-debug.log
  // error_log ( date ( 'Y-m-d H:i:s' ) . "> $msg\n<br />",
  // 3, 'c:/wamp/logs/debug.txt' );
  // fwrite ( $fd, date ( 'Y-m-d H:i:s' ) . "> $msg\n" );
  // fclose ( $fd );
  // 3, '/tmp/webcal-debug.log' );
  // error_log ( date ( 'Y-m-d H:i:s' ) . "> $msg\n",
  // 2, 'sockieman:2000' );
}

/* Looks for URLs in the given text, and makes them into links.
 *
 * @param string $text Input text
 *
 * @return string  The text altered to have HTML links for any web links.
 */
function activate_urls( $text ) {
  return preg_replace( '/[a-z]+:\/\/[^<> \t\r\n]+[a-z0-9\/]/i',
    '<a href="\\0">\\0</a>', $text );
}

/* Adds something to the activity log for an event.
 *
 * The information will be saved to the webcal_entry_log table.
 *
 * @param int    $event_id  Event ID
 * @param string $user      Username of user doing this
 * @param string $user_cal  Username of user whose calendar is affected
 * @param string $type      Type of activity we are logging:
 *   - LOG_APPROVE
 *   - LOG_APPROVE_T
 *   - LOG_ATTACHMENT
 *   - LOG_COMMENT
 *   - LOG_CREATE
 *   - LOG_CREATE_T
 *   - LOG_DELETE
 *   - LOG_DELETE_T
 *   - LOG_LOGIN_FAILURE
 *   - LOG_NEWUSER_FULL
 *   - LOG_NEWUSEREMAIL
 *   - LOG_NOTIFICATION
 *   - LOG_REJECT
 *   - LOG_REJECT_T
 *   - LOG_REMINDER
 *   - LOG_UPDATE
 *   - LOG_UPDATE_T
 *   - LOG_USER_ADD
 *   - LOG_USER_DELETE
 *   - LOG_USER_UPDATE
 * @param string $text     Text comment to add with activity log entry
 */
function activity_log ( $event_id, $user, $user_cal, $type, $text ) {
  $next_id = 1;

  if ( empty ( $type ) ) {
    echo translate ( 'Error Type not set for activity log!' );
    // But don't exit since we may be in mid-transaction.
    return;
  }

  $res = dbi_execute ( 'SELECT MAX( cal_log_id ) FROM webcal_entry_log' );
  if ( $res ) {
    if ( $row = dbi_fetch_row ( $res ) )
      $next_id = $row[0] + 1;

    dbi_free_result ( $res );
  }
  $sql = 'INSERT INTO webcal_entry_log ( cal_log_id, cal_entry_id, cal_login,
    cal_user_cal, cal_type, cal_date, cal_time, cal_text )
    VALUES ( ?, ?, ?, ?, ?, ?, ?, ? )';
  if ( ! dbi_execute ( $sql, array ( $next_id, $event_id, $user,
        ( empty ( $user_cal ) ? null : $user_cal ), $type, gmdate ( 'Ymd' ),
          gmdate ( 'Gis' ), ( empty ( $text ) ? null : $text ) ) ) )
    db_error ( true, $sql );
}

/* Get the corrected timestamp after adding or subtracting ONE_HOUR
 * to compensate for DST.
 */
function add_dstfree_time ( $date, $span, $interval = 1 ) {
  $ctime = date ( 'G', $date );
  $date += $span * $interval;
  $dtime = date ( 'G', $date );
  if ( $ctime == $dtime )
    return $date;
  elseif ( $ctime == 23 && $dtime == 0 )
    $date -= 3600;
  elseif ( ( $ctime == 0 && $dtime == 23 ) || $ctime > $dtime )
    $date += 3600;
  elseif ( $ctime < $dtime )
    $date -= 3600;

  return $date;
}

/* Return the time in HHMMSS format of input time + duration
 *
 * @param string $time   format "235900"
 * @param int $duration  number of minutes
 *
 * @return string  The time in HHMMSS format.
 */
function add_duration ( $time, $duration ) {
  $time = sprintf ( "%06d", $time );
  $minutes =
  intval ( $time / 10000 ) * 60 + ( ( $time / 100 ) % 100 ) + $duration;

  return sprintf ( "%d%02d00", $minutes / 60, $minutes % 60 );
}

/* Builds the HTML for the event label.
 *
 * @param string  $can_access
 * @param string  $time_only
 *
 * @return string  The HTML for the event label
 */
function build_entry_label ( $event, $popupid,
  $can_access, $timestr, $time_only = 'N' ) {
  global $eventinfo, $login, $SUMMARY_LENGTH, $UAC_ENABLED, $user;
  $ret = '';
  // Get reminders display string.
  $reminder = getReminders ( $event->getId (), true );
  $can_access = ( $UAC_ENABLED == 'Y' ? $can_access : 0 );
  $not_my_entry = ( ( $login != $user && strlen ( $user ) ) ||
    ( $login != $event->getLogin () && strlen ( $event->getLogin () ) ) );

  $sum_length = $SUMMARY_LENGTH;
  if ( $event->isAllDay () || $event->isUntimed () )
    $sum_length += 6;

  $tmpAccess = $event->getAccess ();
  $tmpId = $event->getId ();
  $tmpLogin = $event->getLogin ();
  $tmpName = $event->getName ();
  $tmp_ret = htmlspecialchars ( substr ( $tmpName, 0, $sum_length )
     . ( strlen ( $tmpName ) > $sum_length ? '...' : '' ) );

  if ( $not_my_entry && $tmpAccess == 'R' && !
    ( $can_access &PRIVATE_WT ) ) {
    if ( $time_only != 'Y' )
      $ret = '(' . translate ( 'Private' ) . ')';

    // translate ( 'This event is private' )
    $eventinfo .= build_entry_popup ( $popupid, $tmpLogin,
      str_replace ( 'XXX', translate ( 'private' ),
        translate ( 'This event is XXX.' ) ), '' );
  } else
  if ( $not_my_entry && $tmpAccess == 'C' && !
    ( $can_access &CONF_WT ) ) {
    if ( $time_only != 'Y' )
      $ret = '(' . translate ( 'Conf.' ) . ')';

    $eventinfo .= build_entry_popup ( $popupid, $tmpLogin,
      str_replace ( 'XXX', translate ( 'confidential' ),
        translate ( 'This event is XXX.' ) ), '' );
  } else
  if ( $can_access == 0 && $UAC_ENABLED == 'Y' ) {
    if ( $time_only != 'Y' )
      $ret = $tmp_ret;

    $eventinfo .= build_entry_popup ( $popupid, $tmpLogin, '',
      $timestr, '', '', $tmpName, '' );
  } else {
    if ( $time_only != 'Y' )
      $ret = $tmp_ret;

    $eventinfo .= build_entry_popup ( $popupid, $tmpLogin,
      $event->getDescription (), $timestr, site_extras_for_popup ( $tmpId ),
      $event->getLocation (), $tmpName, $tmpId, $reminder );
  }
  return $ret;
}

/* Calculates which row/slot this time represents.
 *
 * This is used in day and week views where hours of the time are separeted
 * into different cells in a table.
 *
 * <b>Note:</b> the global variable <var>$TIME_SLOTS</var> is used to determine
 * how many time slots there are and how many minutes each is. This variable
 * is defined user preferences (or defaulted to admin system settings).
 *
 * @param string $time        Input time in HHMMSS format
 * @param bool   $round_down  Should we change 1100 to 1059?
 *                            (This will make sure a 10AM-100AM appointment just
 *                            shows up in the 10AM slow and not in the 11AM slot
 *                            also.)
 *
 * @return int  The time slot index.
 */
function calc_time_slot ( $time, $round_down = false ) {
  global $TIME_SLOTS;

  $interval = 1440 / $TIME_SLOTS;
  $mins_since_midnight = time_to_minutes ( sprintf ( "%06d", $time ) );
  $ret = intval ( $mins_since_midnight / $interval );
  if ( $round_down && $ret * $interval == $mins_since_midnight )
    $ret--;

  if ( $ret > $TIME_SLOTS )
    $ret = $TIME_SLOTS;

  return $ret;
}

/* Checks for conflicts.
 *
 * Find overlaps between an array of dates and the other dates in the database.
 *
 * Limits on number of appointments: if enabled in System Settings
 * (<var>$LIMIT_APPTS</var> global variable), too many appointments can also
 * generate a scheduling conflict.
 *
 * @todo Update this to handle exceptions to repeating events.
 *
 * @param array  $dates         Array of dates in Timestamp format that is
 *                              checked for overlaps.
 * @param int    $duration      Event duration in minutes
 * @param int    $eventstart    GMT starttime timestamp
 * @param array  $participants  Array of users whose calendars are to be checked
 * @param string $login         The current user name
 * @param int    $id            Current event id (this keeps overlaps from
 *                              wrongly checking an event against itself)
 *
 * @return  Empty string for no conflicts or return the HTML of the
 *          conflicts when one or more are found.
 */
function check_for_conflicts ( $dates, $duration, $eventstart,
  $participants, $login, $id ) {
  global $LIMIT_APPTS, $LIMIT_APPTS_NUMBER, $repeated_events,
  $single_user, $single_user_login, $jumpdate;

  $datecnt = count ( $dates );
  if ( ! $datecnt )
    return false;

  $conflicts = '';
  $count = 0;
  $evtcnt = $found = $query_params = array ();
  $partcnt = count ( $participants );

  $hour = gmdate ( 'H', $eventstart );
  $minute = gmdate ( 'i', $eventstart );

  $allDayStr = translate ( 'All day event' );
  $confidentialStr = translate ( 'Confidential' );
  $exceedsStr = translate ( 'exceeds limit of XXX events per day' );
  $onStr = translate ( 'on' );
  $privateStr = translate ( 'Private' );

  $sql = 'SELECT DISTINCT( weu.cal_login ), we.cal_time, we.cal_duration,
    we.cal_name, we.cal_id, we.cal_access, weu.cal_status, we.cal_date
    FROM webcal_entry we, webcal_entry_user weu WHERE we.cal_id = weu.cal_id AND ( ';

  for ( $i = 0; $i < $datecnt; $i++ ) {
    $sql .= ( $i != 0 ? ' OR ' : '' ) . 'we.cal_date = '
     . gmdate ( 'Ymd', $dates[$i] );
  }
  $sql .= ' ) AND we.cal_time >= 0 AND weu.cal_status IN ( \'A\',\'W\' ) AND ( ';
  if ( $single_user == 'Y' )
    $participants[0] = $single_user_login;
  else
  if ( strlen ( $participants[0] ) == 0 )
    // Likely called from a form with 1 user.
    $participants[0] = $login;

  for ( $i = 0; $i < $partcnt; $i++ ) {
    $sql .= ( $i > 0 ? ' OR ' : '' ) . 'weu.cal_login = ?';
    $query_params[] = $participants[$i];
  }
  // Make sure we don't get something past the end date of the event we're saving.
  $res = dbi_execute ( $sql . ' )', $query_params );
  if ( $res ) {
    $duration1 = sprintf ( "%d", $duration );
    $time1 = sprintf ( "%d%02d00", $hour, $minute );
    while ( $row = dbi_fetch_row ( $res ) ) {
      // Add to an array to see if it has been found already for the next part.
      $found[$count++] = $row[4];
      // See if events overlaps one another.
      if ( $row[4] != $id ) {
        $cntkey = $row[0] . '-' . $row[7];
        $duration2 = $row[2];
        $time2 = sprintf ( "%06d", $row[1] );
        if ( empty ( $evtcnt[$cntkey] ) )
          $evtcnt[$cntkey] = 0;
        else
          $evtcnt[$cntkey]++;

        $over_limit = ( $LIMIT_APPTS == 'Y' && $LIMIT_APPTS_NUMBER > 0 &&
          $evtcnt[$cntkey] >= $LIMIT_APPTS_NUMBER ? 1 : 0 );

        if ( $over_limit ||
          times_overlap ( $time1, $duration1, $time2, $duration2 ) ) {
          $conflicts .= '
            <li>';

          if ( $single_user != 'Y' ) {
            user_load_variables ( $row[0], 'conflict_' );
            $conflicts .= $GLOBALS['conflict_fullname'] . ': ';
          }
          $conflicts .= ( $row[5] == 'C' && $row[0] != $login && !
            $is_assistant && ! $is_nonuser_admin
            // Assistants can see confidential stuff.
            ? '(' . $confidentialStr . ')'
            : ( $row[5] == 'R' && $row[0] != $login
              ? '( ' . $privateStr . ')'
              : '<a href="view_entry.php?id=' . $row[4]
               . ( $row[0] != $login ? '&amp;user=' . $row[0] : '' )
               . '">' . $row[3] . '</a>' ) )
           . ( $duration2 == 1440 && $time2 == 0
            ? ' (' . $allDayStr . ')'
            : ' (' . display_time ( $row[7] . $time2 )
             . ( $duration2 > 0
              ? '-' . display_time ( $row[7]
                 . add_duration ( $time2, $duration2 ) ) : '' ) . ')' )
           . ' ' . $onStr . ' '
           . date_to_str ( date ( 'Ymd', date_to_epoch ( $row[7]
                 . sprintf ( "%06d", $row[1] ) ) ) )
           . ( $over_limit ? ' (' . str_replace ( 'XXX', $LIMIT_APPTS_NUMBER,
              $exceedsStr ) . ')' : '' ) . '</li>';
        }
      }
    }
    dbi_free_result ( $res );
  } else
    db_error ( true );

  for ( $q = 0; $q < $partcnt; $q++ ) {
    // Read repeated events only once for a participant for performance reasons.
    $jumpdate = gmdate ( 'Ymd', $dates[count ( $dates )-1] );
    $repeated_events = query_events ( $participants[$q], true,
      // This date filter is not necessary for functional reasons, but it
      // eliminates some of the events that couldn't possibly match. This could
      // be made much more complex to put more of the searching work onto the
      // database server, or it could be dropped all together to put the
      // searching work onto the client.
      'AND ( we.cal_date <= ' . $jumpdate
       . ' AND ( wer.cal_end IS NULL OR wer.cal_end >= '
       . gmdate ( 'Ymd', $dates[0] ) . ' ) )' );
    for ( $i = 0; $i < $datecnt; $i++ ) {
      $dateYmd = gmdate ( 'Ymd', $dates[$i] );
      $list = get_repeating_entries ( $participants[$q], $dateYmd );
      for ( $j = 0, $listcnt = count ( $list ); $j < $listcnt; $j++ ) {
        // OK we've narrowed it down to a day, now I just gotta check the time...
        // I hope this is right...
        $row = $list[$j];
        if ( $row->getID () != $id && ! in_array ($row->getID (), $found ) &&
            ( $row->getExtForID () == '' || $row->getExtForID () != $id ) ) {
          $time2 = sprintf ( "%06d", $row->getTime () );
          $duration2 = $row->getDuration ();
          if ( times_overlap ( $time1, $duration1, $time2, $duration2 ) ) {
            $conflicts .= '
            <li>';
            if ( $single_user != 'Y' ) {
              user_load_variables ( $row->getLogin (), 'conflict_' );
              $conflicts .= $GLOBALS['conflict_fullname'] . ': ';
            }
            $conflicts .= ( $row->getAccess () == 'C'
              && $row->getLogin () != $login && !
              $is_assistant && ! $is_nonuser_admin
              // Assistants can see confidential stuff.
              ? '(' . $confidentialStr . ')'
              : ( $row->getAccess () == 'R' && $row->getLogin () != $login
                ? '(' . $privateStr . ')'
                : '<a href="view_entry.php?id=' . $row->getID ()
                 . ( ! empty ( $user ) && $user != $login
                  ? '&amp;user=' . $user : '' )
                 . '">' . $row->getName () . '</a>' ) )
             . ' (' . display_time ( $row->getDate () . $time2 )
             . ( $duration2 > 0
              ? '-' . display_time ( $row->getDate ()
                 . add_duration ( $time2, $duration2 ) ) : '' )
             . ')' . ' ' . $onStr . ' ' . date_to_str ( $dateYmd ) . '</li>';
          }
        }
      }
    }
  }

  return $conflicts;
}

/* Replaces unsafe characters with HTML encoded equivalents.
 *
 * @param string $value  Input text
 *
 * @return string  The cleaned text.
 */
function clean_html ( $value ) {
  $value = htmlspecialchars ( $value, ENT_QUOTES );
  $value = strtr ( $value, array (
      '(' => '&#40;',
      ')' => '&#41;'
      ) );
  return $value;
}

/* Removes non-digits from the specified text.
 *
 * @param string $data  Input text
 *
 * @return string  The converted text.
 */
function clean_int ( $data ) {
  return preg_replace ( '/\D/', '', $data );
}

/* Removes whitespace from the specified text.
 *
 * @param string $data  Input text
 *
 * @return string  The converted text.
 */
function clean_whitespace ( $data ) {
  return preg_replace ( '/\s/', '', $data );
}

/* Removes non-word characters from the specified text.
 *
 * @param string $data  Input text
 *
 * @return string  The converted text.
 */
function clean_word ( $data ) {
  return preg_replace ( '/\W/', '', $data );
}

/* Combines the repeating and nonrepeating event arrays and sorts them
 *
 * The returned events will be sorted by time of day.
 *
 * @param array $ev   Array of events
 * @param array $rep  Array of repeating events
 *
 * @return array  Array of Events.
 */
function combine_and_sort_events ( $ev, $rep ) {
  $ids = array ();

  // Repeating events show up in $ev and $rep.
  // Record their ids and don't add them to the combined array.
  foreach ( $rep as $obj ) {
    $ids[] = $obj->getID ();
  }
  foreach ( $ev as $obj ) {
    if ( ! in_array ( $obj->getID (), $ids ) )
     $rep[] = $obj;
  }
  usort ( $rep, 'sort_events' );

  return $rep;
}

/* Draws a daily outlook style availability grid showing events that are
 * approved and awaiting approval.
 *
 * @param string $date          Date to show the grid for
 * @param array  $participants  Which users should be included in the grid
 * @param string $popup         Not used
 *
 * @return string  HTML to display matrix.
 */
function daily_matrix ( $date, $participants, $popup = '' ) {
  global $CELLBG, $ENTRY_SLOTS, $events, $repeated_events, $TABLEBG, $THBG,
  $THFG, $thismonth, $thisyear, $TIME_FORMAT, $TODAYCELLBG, $user_fullname,
  $WORK_DAY_END_HOUR, $WORK_DAY_START_HOUR;

  $allAttendeesStr = translate ( 'All Attendees' );
  $busy = translate ( 'Busy' );
  $cnt = count ( $participants );
  $dateTS = date_to_epoch ( $date );
  $first_hour = $WORK_DAY_START_HOUR;
  $increment = intval ( 1440 /
    ( $ENTRY_SLOTS > 288 ? 288 : ( $ENTRY_SLOTS < 72 ? 72 : $ENTRY_SLOTS ) ) );
  $last_hour = $WORK_DAY_END_HOUR;
  $master = array ();
  $MouseOut = $MouseOver = $str = '';
  $participant_pct = '20%'; //Use percentage.

  $tentative = translate ( 'Tentative' );
  // translate ( 'Schedule an appointment for' )
  $titleStr = ' title="' . translate ( 'Schedule an appointment for XXX.' ) . '">';
  $viewMsg = translate ( 'View this entry' );

  $hours = $last_hour - $first_hour;
  $interval = intval ( 60 / $increment );
  $cell_pct = intval ( 80 / ( $hours * $interval ) );
  $style_width = ( $cell_pct > 0 ? 'style="width:' . $cell_pct . '%;"' : '' );
  $thismonth = date ( 'm', $dateTS );
  $thisyear = date ( 'Y', $dateTS );
  $cols = ( ( $hours * $interval ) + 1 );
  $ret = <<<EOT
    <br />
    <table align="center" class="matrixd" style="width:'80%';" cellspacing="0"
      cellpadding="0">
      <tr>
        <td class="matrix" colspan="{$cols}"></td>
      </tr>
      <tr>
        <th style="width:{$participant_pct};">
EOT;
   $ret .= translate ( 'Participants' ) . '</th>';
  $tentative = translate ( 'Tentative' );
  // translate ( 'Schedule an appointment for' )
  $titleStr = ' title="' . translate ( 'Schedule an appointment for XXX.' ) . '">';
  $viewMsg = translate ( 'View this entry' );

  $hours = $last_hour - $first_hour;
  $interval = intval ( 60 / $increment );
  $cell_pct = intval ( 80 / ( $hours * $interval ) );
  $cols = ( ( $hours * $interval ) + 1 );
  $style_width = ( $cell_pct > 0 ? 'style="width:' . $cell_pct . '%;"' : '' );
  $thismonth = date ( 'm', $dateTS );
  $thisyear = date ( 'Y', $dateTS );

  // Build a master array containing all events for $participants.
  for ( $i = 0; $i < $cnt; $i++ ) {
    /* Pre-Load the repeated events for quckier access. */
    $repeated_events = read_repeated_events ( $participants[$i], $dateTS,
      $dateTS, '' );
    /* Pre-load the non-repeating events for quicker access. */
    $events = read_events ( $participants[$i], $dateTS, $dateTS );

    // Combine events for this date into a single array for easy processing.
    $ALL = array_merge (
      get_repeating_entries ( $participants[$i], $date ),
      get_entries ( $date )
      );
    foreach ( $ALL as $E ) {
      if ( $E->getTime () == 0 ) {
        $duration = 60 * $hours;
        $time = $first_hour . '0000';
      } else {
        $duration = $E->getDuration ();
        $time = date ( 'His', $E->getDateTimeTS () );
      }
      $hour = substr ( $time, 0, 2 );
      $mins = substr ( $time, 2, 2 );

      // Convert cal_time to slot.
      $slot = $hour + substr ( $mins, 0, 1 );

      // Convert cal_duration to bars.
      $bars = $duration / $increment;

      // Never replace 'A' with 'W'.
      for ( $q = 0; $bars > $q; $q++ ) {
        $slot = sprintf ( "%02.2f", $slot );
        if ( strlen ( $slot ) == 4 )
          $slot = '0' . $slot; // Add leading zeros.

        $slot = $slot . ''; // Convert to a string.
        if ( empty ( $master['_all_'][$slot] ) ||
            ( $master['_all_'][$slot]['stat'] != 'A' ) )
          $master['_all_'][$slot]['stat'] = $E->getStatus ();

        if ( empty ( $master[$participants[$i]][$slot] ) ||
            ( $master[$participants[$i]][$slot]['stat'] != 'A' ) ) {
          $master[$participants[$i]][$slot]['stat'] = $E->getStatus ();
          $master[$participants[$i]][$slot]['ID'] = $E->getID ();
        }
        $slot = $slot + ( $increment * .01 );
        if ( $slot - ( int )$slot >= .59 )
          $slot = ( int )$slot + 1;
      }
    }
  }

  for( $i = $first_hour; $i < $last_hour; $i++ ) {
    $hour = $i;
    if ( $TIME_FORMAT == '12' ) {
      $hour %= 12;
      if ( $hour == 0 )
        $hour = 12;

      $hourfmt = '%d';
    } else
      $hourfmt = '%02d';

    $halfway = intval ( ( $interval / 2 ) -1 );
    for( $j = 0; $j < $interval; $j++ ) {
      $inc_x_j = $increment * $j;
      $str .= '
        <td id="C' . ( $j + 1 ) . '" class="dailymatrix" ';
      $tmpTitle = 'onmousedown="schedule_event( ' . $i . ','
       . sprintf ( "%02d", $inc_x_j ) . ' );"' . $MouseOver . $MouseOut
       . str_replace ( 'XXX', sprintf ( $hourfmt, $hour ) . ':' .
          ( $inc_x_j <= 9 ? '0' : '' ) . $inc_x_j, $titleStr );
      switch ( $j ) {
        case $halfway:
          $k = ( $hour <= 9 ? '0' : substr ( $hour, 0, 1 ) );
          $str .= 'style="width:' . $cell_pct . '%; text-align:right;" '
           . $tmpTitle . $k . '</td>';
          break;
        case $halfway + 1:
          $k = ( $hour <= 9 ? substr ( $hour, 0, 1 ) : substr ( $hour, 1, 2 ) );
          $str .= 'style="width:' . $cell_pct . '%; text-align:left;" '
           . $tmpTitle . $k . '</td>';
          break;
        default:
          $str .= $style_width . $tmpTitle . '&nbsp;&nbsp;</td>';
      }
    }
  }
  $ret .= $str . '
      </tr>
      <tr>
        <td class="matrix" colspan="' . $cols . '"></td>
      </tr>';

  // Add user _all_ to beginning of $participants array.
  array_unshift ( $participants, '_all_' );
  // Javascript for cells.
  // Display each participant.
  for ( $i = 0; $i <= $cnt; $i++ ) {
    if ( $participants[$i] != '_all_' ) {
      // Load full name of user.
      user_load_variables ( $participants[$i], 'user_' );

      // Exchange space for &nbsp; to keep from breaking.
      $user_nospace = preg_replace ( '/\s/', '&nbsp;', $user_fullname );
    } else
      $user_nospace = preg_replace ( '/\s/', '&nbsp;', $allAttendeesStr );

    $ret .= '
      <tr>
        <th class="row" style="width:' . $participant_pct . ';">'
     . $user_nospace . '</th>';
    $col = 1;

    // Check each timebar.
    for ( $j = $first_hour; $j < $last_hour; $j++ ) {
      for ( $k = 0; $k < $interval; $k++ ) {
        $r = sprintf ( "%02d", $j ) . '.'
         . sprintf ( "%02d", ( $increment * $k ) ) . '';
        $space = '&nbsp;';

        if ( empty ( $master[$participants[$i]][$r] ) ) {
          // Ignore this..
        } else
        if ( empty ( $master[$participants[$i]][$r]['ID'] ) )
          // This is the first line for 'all' users. No event here.
          $space = '
          <span class="matrix"><img src="images/pix.gif" alt="" /></span>';
        else {
          $tmpMast = $master[$participants[$i]][$r]['stat'];
          if ( strpos ( 'AW', $tmpMast ) !== false )
            $space = '
          <a class="matrix" href="view_entry.php?id='
             . $master[$participants[$i]][$r]['ID']
             . '&friendly=1"><img src="images/pix' . ( $tmpMast = 'A' ? '' : 'b' )
             . '.gif" title="' . $viewMsg . '" alt="' . $viewMsg . '" /></a>';
        }

        $ret .= '
        <td class="matrixappts' . ( $k == '0' ? ' matrixledge' : '' ) . '" '
         . $style_width . ( $space == '&nbsp;' ? ' '
           . 'onmousedown="schedule_event( ' . $j . ','
           . sprintf ( "%02d", ( $increment * $k ) ) . ' );"'
           . " $MouseOver $MouseOut" : '' ) . '>' . $space . '</td>';
        $col++;
      }
    }

    $ret .= '
      </tr>
      <tr>
        <td class="matrix" colspan="' . $cols
     . '"><img src="images/pix.gif" alt="-" /></td>
      </tr>';
  } // End foreach participant.
  return $ret . <<<EOT
    </table><br />
    <table align="center">
      <tr>
        <td class="matrixlegend" ><img src="images/pix.gif" title="{$busy}"
          alt="{$busy}" />{$busy}&nbsp;&nbsp;&nbsp;<img src="images/pixb.gif"
          title="{$tentative}" alt="{$tentative}" />{$tentative}</td>
      </tr>
    </table>
EOT;
}

/* Generate HTML for a date selection for use in a form.
 *
 * @param string $prefix   Prefix to use in front of form element names
 * @param string $date     Currently selected date (in YYYYMMDD format)
 * @param bool $trigger    Add onchange event trigger that
 *                         calls javascript function $prefix_datechanged ()
 * @param int  $num_years  Number of years to display
 *
 * @return string  HTML for the selection box.
 */
function date_selection ( $prefix, $date, $trigger = false, $num_years = 20 ) {
  $selected = ' selected="selected"';
  $trigger_str = ( empty ( $trigger ) ? '' : $prefix . 'datechanged();' );
  $onchange = ( empty ( $trigger_str ) ? '' : 'onchange="$trigger_str"' );
  if ( strlen ( $date ) != 8 )
    $date = date ( 'Ymd' );

  $thisyear = $year = substr ( $date, 0, 4 );
  $thismonth = $month = substr ( $date, 4, 2 );
  $thisday = $day = substr ( $date, 6, 2 );
  if ( $thisyear - date ( 'Y' ) >= ( $num_years - 1 ) )
    $num_years = $thisyear - date ( 'Y' ) + 2;

  $ret = '
      <select name="' . $prefix . 'day" id="' . $prefix . 'day"'
   . $onchange . '>';
  for ( $i = 1; $i <= 31; $i++ ) {
    $ret .= '
        <option value="' . "$i\""
     . ( $i == substr ( $date, 6, 2 ) ? $selected : '' ) . ">$i" . '</option>';
  }
  $ret .= '
      </select>
      <select name="' . $prefix . 'month"' . $onchange . '>';
  for ( $i = 1; $i < 13; $i++ ) {
    $ret .= '
        <option value="' . "$i\""
     . ( $i == substr ( $date, 4, 2 ) ? $selected : '' )
     . '>' . month_name ( $i - 1, 'M' ) . '</option>';
  }
  $ret .= '
      </select>
      <select name="' . $prefix . 'year"' . $onchange . '>';
  for ( $i = -10; $i < $num_years; $i++ ) {
    $y = $thisyear + $i;
    $ret .= '
        <option value="' . "$y\"" . ( $y == $thisyear ? $selected : '' )
     . ">$y" . '</option>';
  }
  return $ret . '
      </select>
      <input type="button" name="' . $prefix . 'btn" onclick="selectDate( \''
   . $prefix . 'day\',\'' . $prefix . 'month\',\'' . $prefix . "year','$date'"
   . ', event, this.form );" value="' . translate ( 'Select' ) . '..." />' . "\n";
}

/* Converts a date to a timestamp.
 *
 * @param string $d   Date in YYYYMMDD or YYYYMMDDHHIISS format
 * @param bool   $gmt Whether to use GMT or LOCAL
 *
 * @return int  Timestamp representing, in UTC or LOCAL time.
 */
function date_to_epoch ( $d , $gmt=true) {
  if ( $d == 0 )
    return 0;

  $dH = $di = $ds = 0;
  if ( strlen ( $d ) == 13 ) { // Hour value is single digit.
    $dH = substr ( $d, 8, 1 );
    $di = substr ( $d, 9, 2 );
    $ds = substr ( $d, 11, 2 );
  }
  if ( strlen ( $d ) == 14 ) {
    $dH = substr ( $d, 8, 2 );
    $di = substr ( $d, 10, 2 );
    $ds = substr ( $d, 12, 2 );
  }

  if ( $gmt )
    return gmmktime ( $dH, $di, $ds,
      substr ( $d, 4, 2 ),
      substr ( $d, 6, 2 ),
      substr ( $d, 0, 4 ) );
  else
    return mktime ( $dH, $di, $ds,
      substr ( $d, 4, 2 ),
      substr ( $d, 6, 2 ),
      substr ( $d, 0, 4 ) );
}

/* Converts a date in YYYYMMDD format into "Friday, December 31, 1999",
 * "Friday, 12-31-1999" or whatever format the user prefers.
 *
 * @param string $indate        Date in YYYYMMDD format
 * @param string $format        Format to use for date
 *                              (default is "__month__ __dd__, __yyyy__")
 * @param bool   $show_weekday  Should the day of week also be included?
 * @param bool   $short_months  Should the abbreviated month names be used
 *                              instead of the full month names?
 * @param bool   $forceTranslate Check to see if there is a translation for
 *            the specified data format.  If there is, then use
 *            the translated format from the language file, but
 *            only if $DATE_FORMAT is language-defined.
 *
 * @return string  Date in the specified format.
 *
 * @global string Preferred date format
 * @TODO Add other date () parameters like ( j, n )
 */
function date_to_str ( $indate, $format = '', $show_weekday = true,
  $short_months = false, $forceTranslate = false ) {
  global $DATE_FORMAT;

  if ( strlen ( $indate ) == 0 )
    $indate = date ( 'Ymd' );

  // If they have not set a preference yet...
  if ( $DATE_FORMAT == '' || $DATE_FORMAT == 'LANGUAGE_DEFINED' )
    $DATE_FORMAT = translate ( '__month__ __dd__, __yyyy__' );
  else if ( $DATE_FORMAT == 'LANGUAGE_DEFINED' &&
    $forceTranslate && $format != '' && translation_exists ( $format ) ) {
    $format = translate ( $format );
  }

  if ( empty ( $format ) )
    $format = $DATE_FORMAT;

  $y = intval ( $indate / 10000 );
  $m = intval ( $indate / 100 ) % 100;
  $d = $indate % 100;
  $wday = strftime ( "%w", mktime ( 0, 0, 0, $m, $d, $y ) );
  if ( $short_months ) {
    $month = month_name ( $m - 1, 'M' );
    $weekday = weekday_name ( $wday, 'D' );
  } else {
    $month = month_name ( $m - 1 );
    $weekday = weekday_name ( $wday );
  }

  $ret = str_replace ( '__dd__', $d, $format );
  $ret = str_replace ( '__j__', intval ( $d ), $ret );
  $ret = str_replace ( '__mm__', $m, $ret );
  $ret = str_replace ( '__mon__', $month, $ret );
  $ret = str_replace ( '__month__', $month, $ret );
  $ret = str_replace ( '__n__', sprintf ( "%02d", $m ), $ret );
  $ret = str_replace ( '__yy__', sprintf ( "%02d", $y % 100 ), $ret );
  $ret = str_replace ( '__yyyy__', $y, $ret );

  return ( $show_weekday ? "$weekday, $ret" : $ret );
}

/* Extracts a user's name from a session id.
 *
 * This prevents users from begin able to edit their cookies.txt file and set
 * the username in plain text.
 *
 * @param string $instr  A hex-encoded string. "Hello" would be "678ea786a5".
 *
 * @return string  The decoded string.
 *
 * @global array Array of offsets
 *
 * @see encode_string
 */
function decode_string ( $instr ) {
  global $offsets;

  $cntOffsets = count ( $offsets );
  $orig = '';
  for ( $i = 0, $cnt = strlen ( $instr ); $i < $cnt; $i += 2 ) {
    $orig .= chr (
      ( hextoint ( substr ( $instr, $i, 1 ) ) * 16 +
        hextoint ( substr ( $instr, $i + 1, 1 ) ) - $offsets[
        ( $i / 2 ) % $cntOffsets ] + 256 ) % 256 );
  }
  return $orig;
}

/* Display a text for a single activity log entry.
 *
 * @param string $cal_type  the log entry type
 * @param string $cal_text  addiitonal text to display
 *
 * @return string  HTML for one log entry.
 */
function display_activity_log ( $cal_type, $cal_text = '', $break = '<br/>&nbsp;' ) {
  if ( $cal_type == LOG_APPROVE )
    $ret = translate ( 'Event approved' );
  elseif ( $cal_type == LOG_APPROVE_J )
    $ret = translate ( 'Journal approved' );
  elseif ( $cal_type == LOG_APPROVE_T )
    $ret = translate ( 'Task approved' );
  elseif ( $cal_type == LOG_ATTACHMENT )
    $ret = translate ( 'Attachment' );
  elseif ( $cal_type == LOG_COMMENT )
    $ret = translate ( 'Comment' );
  elseif ( $cal_type == LOG_CREATE )
    $ret = translate ( 'Event created' );
  elseif ( $cal_type == LOG_CREATE_J )
    $ret = translate ( 'Journal created' );
  elseif ( $cal_type == LOG_CREATE_T )
    $ret = translate ( 'Task created' );
  elseif ( $cal_type == LOG_DELETE )
    $ret = translate ( 'Event deleted' );
  elseif ( $cal_type == LOG_DELETE_J )
    $ret = translate ( 'Journal deleted' );
  elseif ( $cal_type == LOG_DELETE_T )
    $ret = translate ( 'Task deleted' );
  elseif ( $cal_type == LOG_LOGIN_FAILURE )
    $ret = translate ( 'Invalid login' );
  elseif ( $cal_type == LOG_NEWUSER_EMAIL )
    $ret = translate ( 'New user via email (self registration)' );
  elseif ( $cal_type == LOG_NEWUSER_FULL )
    $ret = translate ( 'New user (self registration)' );
  elseif ( $cal_type == LOG_NOTIFICATION )
    $ret = translate ( 'Notification sent' );
  elseif ( $cal_type == LOG_REJECT )
    $ret = translate ( 'Event rejected' );
  elseif ( $cal_type == LOG_REJECT_J )
    $ret = translate ( 'Journal rejected' );
  elseif ( $cal_type == LOG_REJECT_T )
    $ret = translate ( 'Task rejected' );
  elseif ( $cal_type == LOG_REMINDER )
    $ret = translate ( 'Reminder sent' );
  elseif ( $cal_type == LOG_UPDATE )
    $ret = translate ( 'Event updated' );
  elseif ( $cal_type == LOG_UPDATE_J )
    $ret = translate ( 'Journal updated' );
  elseif ( $cal_type == LOG_UPDATE_T )
    $ret = translate ( 'Task updated' );
  elseif ( $cal_type == LOG_USER_ADD )
    $ret = translate ( 'Add User' );
  elseif ( $cal_type == LOG_USER_DELETE )
    $ret = translate ( 'Delete User' );
  elseif ( $cal_type == LOG_USER_UPDATE )
    $ret = translate ( 'Edit User' );
  else
    $ret = '???';
  //fix any broken special characters
  $cal_text =  preg_replace("/&amp;(#[0-9]+|[a-z]+);/i", "&$1;", htmlentities ( $cal_text ));
  return $ret
   . ( empty ( $cal_text ) ? '' : $break . $cal_text );
}

/* Display the <<Admin link on pages if menus are not enabled
 *
 * @param bool $break  If true, include break if empty
 *
 * @return string  HTML for Admin Home link
 * @global string  (Y/N) Is the Top Menu Enabled
 */
function display_admin_link ( $break = true ) {
  global $MENU_ENABLED;

  $adminStr = translate ( 'Admin' );

  return ( $break ? '<br />' . "\n" : '' )
   . ( $MENU_ENABLED == 'N' ? '<a title="' . $adminStr
     . '" class="nav" href="adminhome.php">&laquo;&nbsp; ' . $adminStr
     . '</a><br /><br />' . "\n" : '' );
}

/* Generate HTML to create a month display.
 */
function display_month ( $thismonth, $thisyear, $demo = false ) {
  global $DISPLAY_ALL_DAYS_IN_MONTH, $DISPLAY_LONG_DAYS, $DISPLAY_WEEKNUMBER,
  $login, $today, $user, $WEEK_START, $WEEKENDBG;

  $ret = '
    <table class="main" cellspacing="0" cellpadding="0" id="month_main">
      <tr>' . ( $DISPLAY_WEEKNUMBER == 'Y' ? '
        <th class="empty"></th>' : '' );

  for ( $i = 0; $i < 7; $i++ ) {
    $thday = ( $i + $WEEK_START ) % 7;
    $ret .= '
        <th' . ( is_weekend ( $thday ) ? ' class="weekend"' : '' )
     . '>' . weekday_name ( $thday, $DISPLAY_LONG_DAYS ) . '</th>';
  }
  $ret .= '
      </tr>';
  $charset = translate ( 'charset' );
  $weekStr = translate ( 'Week' );
  $WKStr = translate ( 'WK' );

  $wkstart = get_weekday_before ( $thisyear, $thismonth );
  // Generate values for first day and last day of month.
  $monthstart = date ( 'Ymd', mktime ( 0, 0, 0, $thismonth, 1, $thisyear ) );
  $monthend = date ( 'Ymd', mktime ( 0, 0, 0, $thismonth + 1, 0, $thisyear ) );
  $monthend2 = date ( 'Ymd His', mktime ( 0, 0, 0, $thismonth + 1, 0, $thisyear ) );
  $todayYmd = date ( 'Ymd', $today );
  for ( $i = $wkstart; date ( 'Ymd', $i + 43200 ) <= $monthend; $i += 604800 ) {
    $ret .= '
      <tr>';
    if ( $DISPLAY_WEEKNUMBER == 'Y' ) {
      $tmp = date ( 'W', $i + 86400 );
      $ret .= '
        <td class="weekcell"><a title="' . $weekStr . ' ' . $tmp . '" href="'
       . ( $demo ? '' : 'week.php?date=' . date ( 'Ymd', $i + 86400 ) )
       . ( ! empty ( $user ) && $user != $login ? '&amp;user=' . $user : '' )
       . ( empty ( $cat_id ) ? '' : '&amp;cat_id=' . $cat_id ) . '"' . '>';

      $wkStr = $WKStr . $tmp;
      $wkStr2 = '';

      if ( $charset == 'UTF-8' )
        $wkStr2 = $wkStr;
      else {
        for ( $w = 0, $cnt = strlen ( $wkStr ); $w < $cnt; $w++ ) {
          $wkStr2 .= substr ( $wkStr, $w, 1 ) . '<br />';
        }
      }
      $ret .= $wkStr2 . '</a></td>';
    }

    for ( $j = 0; $j < 7; $j++ ) {
      $date = $i + ( $j * 86400 + 43200 );
      $dateYmd = date ( 'Ymd', $date );
      $dateD = date ( 'd', $date );
      $thiswday = date ( 'w', $date );
      $is_weekend = is_weekend ( $date ) && ( ! empty ( $WEEKENDBG ) );
      $ret .= '
        <td';

      $currMonth = ( $dateYmd >= $monthstart && $dateYmd <= $monthend );
      if ( $currMonth ||
        ( ! empty ( $DISPLAY_ALL_DAYS_IN_MONTH ) && $DISPLAY_ALL_DAYS_IN_MONTH == 'Y' ) ) {
        $class = ( $currMonth
          ? ( ! $demo && $dateYmd == $todayYmd ? 'today' : ( $is_weekend ? 'weekend' : '' ) )
          : 'othermonth' );

        // Get events for this day.
        $ret_events = '';
        if ( ! $demo )
          $ret_events = print_date_entries ( $dateYmd,
            ( empty ( $user ) ? $login : $user ), false );
        else {
          // Since we base this calendar on the current month,
          // the placement of the days always change so
          // set 3rd Thursday as "today" for the demo...
          if ( $dateD > 15 && $dateD < 23 && $thiswday == 4 ) {
            $class = 'today';
            $ret_events = translate ( 'Today' );
          }
          // ... and set 2nd Saturday and 2nd Tuesday as the demo event days.
          if ( $dateD > 7 && $dateD < 16 &&
            ( $thiswday == 2 || $thiswday == 6 ) ) {
            $class .= ' entry hasevents';
            $ret_events = translate ( 'My event text' );
          }
        }
        $class = trim ( $class );
        $class .= ( ! empty ( $ret_events ) &&
          strstr ( $ret_events, 'class="entry"' ) ? ' hasevents' : '' );

        $ret .= ( strlen ( $class ) ? ' class="' . $class . '"' : '' )
         . '>' . $ret_events . '</td>';
      } else
        $ret .= ( $is_weekend ? ' class="weekend"' : '' ) . '>&nbsp;</td>';
    }
    $ret .= '
      </tr>';
  }
  return $ret . '
    </table>';
}

/* Generate the HTML for the navigation bar.
 */
function display_navigation ( $name, $show_arrows = true, $show_cats = true ) {
  global $cat_id, $CATEGORIES_ENABLED, $caturl, $DATE_FORMAT_MY,
  $DISPLAY_SM_MONTH, $DISPLAY_TASKS, $DISPLAY_WEEKNUMBER, $is_admin,
  $is_assistant, $is_nonuser_admin, $login, $nextYmd, $nowYmd, $prevYmd,
  $single_user, $spacer, $thisday, $thismonth, $thisyear, $user, $user_fullname,
  $wkend, $wkstart;

  if ( empty ( $name ) )
    return;

  $nextStr = translate ( 'Next' );
  $prevStr = translate ( 'Previous' );
  $u_url = ( ! empty ( $user ) && $user != $login
    ? 'user=' . $user . '&amp;' : '' );
  $ret = '
      <div class="topnav"'
  // Hack to prevent giant space between minicals and navigation in IE.
  . ( get_web_browser () == 'MSIE' ? ' style="zoom:1"' : '' )
   . '>' . ( $show_arrows &&
    ( $name != 'month' || $DISPLAY_SM_MONTH == 'N' || $DISPLAY_TASKS == 'Y' ) ? '
        <a title="' . $nextStr . '" class="next" href="' . $name . '.php?'
     . $u_url . 'date=' . $nextYmd . $caturl
     . '"><img src="images/rightarrow.gif" alt="' . $nextStr . '" /></a>
        <a title="' . $prevStr . '" class="prev" href="' . $name . '.php?'
     . $u_url . 'date=' . $prevYmd . $caturl
     . '"><img src="images/leftarrow.gif" alt="' . $prevStr . '" /></a>' : '' ) . '
        <div class="title">
          <span class="date">';

  if ( $name == 'day' )
    $ret .= date_to_str ( $nowYmd );
  elseif ( $name == 'week' )
    $ret .= date_to_str ( date ( 'Ymd', $wkstart ), '', false )
     . '&nbsp;&nbsp;&nbsp; - &nbsp;&nbsp;&nbsp;'
     . date_to_str ( date ( 'Ymd', $wkend - 86400 ), '', false )
     . ( $DISPLAY_WEEKNUMBER == 'Y' ? " \n(" . translate ( 'Week' ) . ' '
       . date ( 'W', $wkstart + 86400 ) . ')' : '' );
  elseif ( $name == 'month' || $name == 'view_l' ) {
    $ret .= $spacer
     . date_to_str ( sprintf ( "%04d%02d01", $thisyear, $thismonth ),
      $DATE_FORMAT_MY, false, false, true );
  }

  return $ret . '</span>
          <span class="user">'
  // Display current calendar's user (if not in single user).
  . ( $single_user == 'N' ? '<br />' . $user_fullname : '' )
   . ( $is_nonuser_admin ||
    ( $is_admin && ! empty ( $user ) && $user == '__public__' )
    ? '<br />-- ' . translate ( 'Admin mode' ) . ' --' : '' )
   . ( $is_assistant
    ? '<br />-- ' . translate ( 'Assistant mode' ) . ' --' : '' ) . '</span>'
   . ( $CATEGORIES_ENABLED == 'Y' && $show_cats &&
    ( ! $user || ( $user == $login || $is_assistant ) ) ? '<br /><br />'
     . print_category_menu ( $name,
      sprintf ( "%04d%02d%02d", $thisyear, $thismonth, $thisday ),
      $cat_id ) : '' ) . '
        </div>
      </div><br />';
}

/* Prints out a minicalendar for a month.
 *
 * @todo Make day.php NOT be a special case
 *
 * @param int    $thismonth      Number of the month to print
 * @param int    $thisyear       Number of the year
 * @param bool   $showyear       Show the year in the calendar's title?
 * @param bool   $show_weeknums  Show week numbers to the left of each row?
 * @param string $minical_id     id attribute for the minical table
 * @param string $month_link     URL and query string for month link that should
 *                               come before the date specification (e.g.
 *                               month.php?  or  view_l.php?id=7&amp;)
 */
function display_small_month ( $thismonth, $thisyear, $showyear,
  $show_weeknums = false, $minical_id = '', $month_link = 'month.php?' ) {
  global $boldDays, $caturl, $DATE_FORMAT_MY, $DISPLAY_ALL_DAYS_IN_MONTH,
  $DISPLAY_TASKS, $DISPLAY_WEEKNUMBER, $get_unapproved, $login,
  $MINI_TARGET, // Used by minical.php
  $SCRIPT, $SHOW_EMPTY_WEEKENDS,//Used by year.php
  $thisday, // Needed for day.php
  $today, $use_http_auth, $user, $WEEK_START;

  $nextStr = translate ( 'Next' );
  $prevStr = translate ( 'Previous' );
  $u_url = ( $user != $login && ! empty ( $user )
    ? 'user=' . $user . '&amp;' : '' );
  $weekStr = translate ( 'Week' );

  // Start the minical table for each month.
  $ret = '
    <table class="minical"'
   . ( $minical_id != '' ? ' id="' . $minical_id . '"' : '' ) . '>';

  $monthstart = date ( 'Ymd', mktime ( 0, 0, 0, $thismonth, 1, $thisyear ) );
  $monthend = date ( 'Ymd', mktime ( 0, 0, 0, $thismonth + 1, 0, $thisyear ) );
  // Determine if the week starts on Sunday or Monday.
  // TODO:  We need to be able to start a week on ANY day.
  $wkstart = get_weekday_before ( $thisyear, $thismonth );

  if ( $SCRIPT == 'day.php' ) {
    $month_ago =
    date ( 'Ymd', mktime ( 0, 0, 0, $thismonth - 1, 1, $thisyear ) );
    $month_ahead =
    date ( 'Ymd', mktime ( 0, 0, 0, $thismonth + 1, 1, $thisyear ) );

    $ret .= '<caption>' . $thisday . '</caption>
      <thead>
        <tr class="monthnav">
          <th colspan="' . ( $DISPLAY_WEEKNUMBER == true ? 8 : 7 ) . '">
            <a title="' . $prevStr . '" class="prev" href="day.php?' . $u_url
     . 'date=' . $month_ago . $caturl
     . '"><img src="images/leftarrowsmall.gif" alt="' . $prevStr . '" /></a>
            <a title="' . $nextStr . '" class="next" href="day.php?' . $u_url
     . 'date=' . $month_ahead . $caturl
     . '"><img src="images/rightarrowsmall.gif" alt="' . $nextStr . '" /></a>'
     . date_to_str ( sprintf ( "%04d%02d%02d", $thisyear, $thismonth, 1 ),
      ( $showyear != '' ? $DATE_FORMAT_MY : '__month__' ), false ) . '
          </th>
        </tr>';
  } elseif ( $SCRIPT == 'minical.php' ) {
    $month_ago =
    date ( 'Ymd', mktime ( 0, 0, 0, $thismonth - 1, $thisday, $thisyear ) );
    $month_ahead =
    date ( 'Ymd', mktime ( 0, 0, 0, $thismonth + 1, $thisday, $thisyear ) );

    $ret .= '
      <thead>
        <tr class="monthnav">
          <th colspan="7">
            <a title="' . $prevStr . '" class="prev" href="minical.php?'
     . $u_url . 'date=' . $month_ago
     . '"><img src="images/leftarrowsmall.gif" alt="' . $prevStr . '" /></a>
            <a title="' . $nextStr . '" class="next" href="minical.php?'
     . $u_url . 'date=' . $month_ahead
     . '"><img src="images/rightarrowsmall.gif" alt="' . $nextStr . '" /></a>'
     . date_to_str ( sprintf ( "%04d%02d%02d", $thisyear, $thismonth, 1 ),
      ( $showyear != '' ? $DATE_FORMAT_MY : '__month__' ), false ) . '
          </th>
        </tr>';
  } else // Not day or minical script. Print the month name.
    $ret .= '
      <caption><a href="' . $month_link . $u_url . 'year=' . $thisyear
     . '&amp;month=' . $thismonth . '">'
     . date_to_str ( sprintf ( "%04d%02d%02d", $thisyear, $thismonth, 1 ),
      ( $showyear != '' ? $DATE_FORMAT_MY : '__month__' ), false )
     . '</a></caption>
      <thead>';

  $ret .= '
        <tr>'
  // Print the headers to display the day of the week (Sun, Mon, Tues, etc.).
  // If we're showing week numbers we need an extra column.
  . ( $show_weeknums && $DISPLAY_WEEKNUMBER == 'Y' ? '
          <th class="empty">&nbsp;</th>' : '' );

  for ( $i = 0; $i < 7; $i++ ) {
    $thday = ( $i + $WEEK_START ) % 7;
    $ret .= '
          <th' . ( is_weekend ( $thday ) ? ' class="weekend"' : '' ) . '>'
     . weekday_name ( $thday, 'D' ) . '</th>';
  }
  // End the header row.
  $ret .= '
        </tr>
      </thead>
      <tbody>';
  for ( $i = $wkstart; date ( 'Ymd', $i ) <= $monthend; $i += 604800 ) {
    $tmp = $i + 172800; // 48 hours.
    $ret .= '
        <tr>' . ( $show_weeknums && $DISPLAY_WEEKNUMBER == 'Y' ? '
          <td><a class="weeknumber" ' . 'title="' . $weekStr . '&nbsp;'
       . date ( 'W', $i + 86400 ) . '" ' . 'href="week.php?' . $u_url . 'date='
       . date ( 'Ymd', $tmp ) . '">(' . date ( 'W', $tmp ) . ')</a></td>' : '' );

    for ( $j = 0; $j < 7; $j++ ) {
      // Add 12 hours just so we don't have DST problems.
      $date = $i + ( $j * 86400 + 43200 );
      $dateYmd = date ( 'Ymd', $date );
      $hasEvents = false;
      $title = '';
      $ret .= '
          <td';

      if ( $boldDays ) {
        $ev = get_entries ( $dateYmd, $get_unapproved, true, true );
        if ( count ( $ev ) > 0 ) {
          $hasEvents = true;
          $title = $ev[0]->getName ();
        } else {
          $rep = get_repeating_entries ( $user, $dateYmd, $get_unapproved );
          if ( count ( $rep ) > 0 ) {
            $hasEvents = true;
            $title = $rep[0]->getName ();
          }
        }
      }
      if ( ( $dateYmd >= $monthstart && $dateYmd <= $monthend ) ||
          ( ! empty ( $DISPLAY_ALL_DAYS_IN_MONTH ) &&
            $DISPLAY_ALL_DAYS_IN_MONTH == 'Y' ) ) {
        $class =
        // If it's a weekend.
        ( is_weekend ( $date ) ? 'weekend' : '' )
        // If the day being viewed is today AND script = day.php.
        . ( $dateYmd == $thisyear . $thismonth . $thisday && $SCRIPT == 'day.php'
          ? ' selectedday' : '' )
        // Are there any events scheduled for this date?
        . ( $hasEvents ? ' hasevents' : '' );

        $ret .= ( $class != '' ? ' class="' . $class . '"' : '' )
         . ( $dateYmd == date ( 'Ymd', $today ) ? ' id="today"' : '' )
         . '><a href="';

        if ( $SCRIPT == 'minical.php' )
          $ret .= ( $use_http_auth
            ? 'day.php?user=' . $user
            : 'nulogin.php?login=' . $user . '&amp;return_path=day.php' )
           . '&amp;date=' . $dateYmd . '"'
           . ( empty ( $MINI_TARGET ) ? '' : ' target="' . $MINI_TARGET . '"' )
           . ( empty ( $title ) ? '' : ' title="' . $title . '"' );
        else
          $ret .= 'day.php?' . $u_url . 'date=' . $dateYmd . '"';

        $ret .= '>' . date ( 'j', $date ) . '</a></td>';
      } else
        $ret .= ' class="empty' . ( ! empty ( $SHOW_EMPTY_WEEKENDS )
          && is_weekend ( $date ) ? ' weekend' : '' ) . '">&nbsp;</td>';
    } // end for $j
    $ret .= '
        </tr>';
  } // end for $i
  return $ret . '
      </tbody>
    </table>';
}

/* Prints small task list for this $login user.
 */
function display_small_tasks ( $cat_id ) {
  global $caturl, $DATE_FORMAT_TASK, $eventinfo,
  $is_assistant, $login, $task_filter, $user;
  static $key = 0;

  if ( ! empty ( $user ) && $user != $login && ! $is_assistant )
    return false;

  $SORT_TASKS = 'Y';

  $pri[1] = translate ( 'High' );
  $pri[2] = translate ( 'Medium' );
  $pri[3] = translate ( 'Low' );
  $task_user = $login;
  $u_url = '';

  if ( $user != $login && ! empty ( $user ) ) {
    $u_url = 'user=' . $user . '&amp;';
    $task_user = $user;
  }
  $ajax = array ();
  $dueSpacer = '&nbsp;';
  $task_cat = ( empty ( $cat_id ) ? -99 : $cat_id );

  if ( $SORT_TASKS == 'Y' ) {
    for ( $i = 0; $i < 4; $i++ ) {
      $ajax[$i] = '
        <td class="sorter" onclick="sortTasks( ' . $i . ', ' . $task_cat
       . ', this )"><img src="images/up.png" style="vertical-align:bottom" /></td>';
      $ajax[$i + 4] = '
        <td  class="sorter sorterbottom" onclick="sortTasks( ' .
      ( $i + 4 ) . ', ' . $task_cat
       . ', this )"><img src="images/down.png" style="vertical-align:top" /></td>';
    }
  } else {
    $dueSpacer = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
    $ajax = array_pad ( $ajax, 8, '
        <td></td>' );
  }

  $priorityStr = translate ( 'Priority' );
  $dateFormatStr = $DATE_FORMAT_TASK;
  $task_list = query_events ( $task_user, false,
    ( empty ( $task_filter ) ? '' : $task_filter ), $cat_id, true );
  $row_cnt = 1;
  $task_html = '
    <table class="minitask" cellspacing="0" cellpadding="2">
      <tr class="header">
        <th colspan="6">' . translate ( 'TASKS' ) . '</th>
        <th align="right" colspan="2"><a href="edit_entry.php?' . $u_url
   . 'eType=task' . $caturl
   . '"><img src="images/new.gif" alt="+" class="new" /></a></th>
      </tr>
      <tr class="header">
        <td rowspan="2" class="sorterbottom">!&nbsp;</td>' . $ajax[0] . '
        <td rowspan="2" width="20%" class="sorterbottom">'
   . translate ( 'Task_Title' )
   . '&nbsp;</td>' . $ajax[1] . '
        <td rowspan="2" class="sorterbottom">' . translate ( 'Due' )
   . $dueSpacer . '</td>'
   . $ajax[2] . '
        <td rowspan="2" class="sorterbottom">%</td>' . $ajax[3] . '
      </tr>
      <tr class="header">' . $ajax[4] . $ajax[5] . $ajax[6] . $ajax[7] . '
      </tr>';
  foreach ( $task_list as $E ) {
    // Check UAC.
    $task_owner = $E->getLogin ();
    if ( access_is_enabled () ) {
      $can_access = access_user_calendar ( 'view', $task_owner, '',
        $E->getCalType (), $E->getAccess () );
      if ( $can_access == 0 )
        continue;
    }
    $cal_id = $E->getId ();
    // Generate popup info.
    $linkid = 'pop' . "$cal_id-$key";
    $key++;
    $link = '<a href="view_entry.php?'
     . ( $task_owner != $login ? 'user=' . $task_owner . '&amp;' : '' )
     . 'id=' . $cal_id . '"';
    $task_html .= '
      <tr class="task" id="' . $linkid . '" style="background-color:'
     . rgb_luminance ( $GLOBALS['BGCOLOR'], $E->getPriority () ) . '">
        <td colspan="2">' . $link . ' title="' . $priorityStr . '">'
     . $E->getPriority () . '</a></td>
        <td class="name" colspan="2" width="50%">&nbsp;' . $link . ' title="'
     . translate ( 'Task Name' ) . ': ' . $E->getName () . '">'
     . substr ( $E->getName (), 0, 15 )
     . ( strlen ( $E->getName () ) > 15 ? '...' : '' ) . '</a></td>
        <td colspan="2">' . $link . ' title="' . translate ( 'Task Due Date' )
     . '">'
     . date_to_str ( $E->getDueDate (), $dateFormatStr, false, false ) . '</a>'
     . '</td>
        <td class="pct" colspan="2">' . $link . ' title="% '
     . translate ( 'Completed' ) . '">' . $E->getPercent () . '</a></td>
      </tr>';
    $row_cnt++;
    // Build special string to pass to popup.
    // TODO:  Move this logic into build_entry_popup ().
    $eventinfo .= build_entry_popup ( 'eventinfo-' . $linkid, $E->getLogin (),
      $E->getDescription (), translate ( 'Due Time' ) . ':'
       . display_time ( '', 0, $E->getDueDateTimeTS () ) . '</dd><dd>'
       . translate ( 'Due Date' ) . ':'
       . date_to_str ( $E->getDueDate (), '', false )
       . "</dd>\n<dt>" . $priorityStr . ":</dt>\n<dd>" . $E->getPriority ()
       . '-' . $pri[ceil ( $E->getPriority () / 3 )] . "</dd>\n<dt>"
       . translate ( 'Percent Complete' ) . ":</dt>\n<dd>" . $E->getPercent ()
       . '%', '', $E->getLocation (), $E->getName (), $cal_id );
  }
  for ( $i = 7; $i > $row_cnt; $i-- ) {
    $task_html .= '<tr><td colspan="8" class="filler">&nbsp;</td></tr>' . "\n";
  }
  $task_html .= "</table>\n";
  return $task_html;
}

/* Displays a time in either 12 or 24 hour format.
 *
 * @param string $time       Input time in HHMMSS format
 *                           Optionally, the format can be YYYYMMDDHHMMSS
 * @param int   $control     bitwise command value
 *   0 default
 *   1 ignore_offset Do not use the timezone offset
 *   2 show_tzid Show abbrev TZ id ie EST after time
 *   4 use server's timezone
 * @param int    $timestamp  optional input time in timestamp format
 * @param string $format     user's TIME_FORMAT when sending emails
 *
 * @return string  The time in the user's timezone and preferred format.
 */
function display_time ( $time = '', $control = 0, $timestamp = '',
  $format = '' ) {
  global $SERVER_TIMEZONE, $TIME_FORMAT;

  if ( $control & 4 ) {
    $currentTZ = getenv ( 'TZ' );
    set_env ( 'TZ', $SERVER_TIMEZONE );
  }
  $t_format = ( empty ( $format ) ? $TIME_FORMAT : $format );
  $tzid = date ( ' T' ); //Default tzid for today.

  if ( ! empty ( $time ) && strlen ( $time ) > 12 )
    $timestamp = date_to_epoch ( $time );

  if ( ! empty ( $timestamp ) ) {
    $time = date ( 'His', $timestamp );
    $tzid = date ( ' T', $timestamp );
    // $control & 1 = do not do timezone calculations
    if ( $control & 1 ) {
      $time = gmdate ( 'His', $timestamp );
      $tzid = ' GMT';
    }
  }
  $hour = intval ( $time / 10000 );
  $min = abs ( ( $time / 100 ) % 100 );

  // Prevent goofy times like 8:00 9:30 9:00 10:30 10:00.
  if ( $time < 0 && $min > 0 )
    $hour--;
  while ( $hour < 0 ) {
    $hour += 24;
  }
  while ( $hour > 23 ) {
    $hour -= 24;
  }
  if ( $t_format == '12' ) {
    $ampm = translate ( $hour >= 12 ? 'pm' : 'am' );
    $hour %= 12;
    if ( $hour == 0 )
      $hour = 12;

    $ret = sprintf ( "%d:%02d%s", $hour, $min, $ampm );
  } else
    $ret = sprintf ( "%02d&#58;%02d", $hour, $min );

  if ( $control & 2 )
    $ret .= $tzid;

  // Reset timezone to previous value.
  if ( ! empty ( $currentTZ ) )
    set_env ( 'TZ', $currentTZ );

  return $ret;
}

/* Checks for any unnaproved events.
 *
 * If any are found, display a link to the unapproved events
 * (where they can be approved).
 *
 * If the user is an admin user, also count up any public events.
 * If the user is a nonuser admin, count up events on the nonuser calendar.
 *
 * @param string $user  Current user login
 */
function display_unapproved_events ( $user ) {
  global $is_admin, $is_nonuser, $login, $MENU_ENABLED,
  $NONUSER_ENABLED, $PUBLIC_ACCESS;
  static $retval;

  // Don't do this for public access login,
  // admin user must approve public events if UAC is not enabled.
  if ( $user == '__public__' || $is_nonuser )
    return;

  // Don't run this more than once.
  if ( ! empty ( $retval[$user] ) )
    return $retval[$user];

  $app_user_hash = $app_users = $query_params = array ();
  $query_params[] = $user;
  $ret = '';
  $sql = 'SELECT COUNT( weu.cal_id ) FROM webcal_entry_user weu, webcal_entry we
    WHERE weu.cal_id = we.cal_id AND weu.cal_status = \'W\'
    AND ( weu.cal_login = ?'
   . ( $PUBLIC_ACCESS == 'Y' && $is_admin && ! access_is_enabled ()
    ? ' OR weu.cal_login = \'__public__\'' : '' );

  if ( access_is_enabled () ) {
    $app_user_hash[$login] = 1;
    $app_users[] = $login;

    $all = ( $NONUSER_ENABLED == 'Y'
      // TODO:  Add 'approved' switch to these functions.
      ? array_merge ( get_my_users (), get_my_nonusers () ) : get_my_users () );

    for ( $j = 0, $cnt = count ( $all ); $j < $cnt; $j++ ) {
      $x = $all[$j]['cal_login'];
      if ( access_user_calendar ( 'approve', $x ) &&
          empty ( $app_user_hash[$x] ) ) {
        $app_user_hash[$x] = 1;
        $app_users[] = $x;
      }
    }
    for ( $i = 0, $cnt = count ( $app_users ); $i < $cnt; $i++ ) {
      $query_params[] = $app_users[$i];
      $sql .= ' OR weu.cal_login = ? ';
    }
  } else
  if ( $NONUSER_ENABLED == 'Y' ) {
    $admincals = get_my_nonusers ( $login );
    for ( $i = 0, $cnt = count ( $admincals ); $i < $cnt; $i++ ) {
      $query_params[] = $admincals[$i]['cal_login'];
      $sql .= ' OR weu.cal_login = ? ';
    }
  }
  $rows = dbi_get_cached_rows ( $sql . ' )', $query_params );
  if ( $rows ) {
    $row = $rows[0];
    if ( $row && $row[0] > 0 )
      $ret .= ( $MENU_ENABLED == 'N'
        ? '<a class="nav" href="list_unapproved.php'
         . ( $user != $login ? '?user=' . $user . '"' : '' )
         . '">' . str_replace ( 'XXX', $row[0],
          translate ( 'You have XXX unapproved entries' ) ) . "</a><br />\n"
        : // Return something that won't display in bottom menu
        // but still has strlen > 0.
        '<!--NOP-->' );
  }

  $retval[$user] = $ret;

  return $ret;
}

/* Sends a redirect to the specified page.
 * The database connection is closed and execution terminates in this function.
 *
 * <b>Note:</b>  MS IIS/PWS has a bug that does not allow sending a cookie and a
 * redirect in the same HTTP header. When we detect that the web server is IIS,
 * we accomplish the redirect using meta-refresh.
 * See the following for more info on the IIS bug:
 * {@link http://www.faqts.com/knowledge_base/view.phtml/aid/9316/fid/4}
 *
 * @param string $url  The page to redirect to. In theory, this should be an
 *                     absolute URL, but all browsers accept relative URLs
 *                     (like "month.php").
 *
 * @global string    Type of webserver
 * @global array     Server variables
 * @global resource  Database connection
 */
function do_redirect ( $url ) {
  global $_SERVER, $c, $SERVER_SOFTWARE, $SERVER_URL;

  // Replace any '&amp;' with '&' since we don't want that in the HTTP header.
  $url = str_replace ( '&amp;', '&', $url );

  if ( empty ( $SERVER_SOFTWARE ) )
    $SERVER_SOFTWARE = $_SERVER['SERVER_SOFTWARE'];

  // $SERVER_URL should end in '/', but we may not have it yet if we are
  // redirecting to the login.  If not, then pull it from the database.
  if ( empty ( $SERVER_URL ) && ! empty ( $c ) ) {
    $res = dbi_query ( "SELECT cal_value FROM webcal_config " .
      "WHERE cal_setting = 'SERVER_URL'" );
    if ( $res ) { 
      if ( $row = dbi_fetch_row ( $res ) ) {
        $SERVER_URL = $row[0];
      }
    }
    dbi_free_result ( $res );
  }

  // If we have the server URL, then use a full URL, which is technically
  // required (but all browsers accept relative URLs here).
  // BUT, only do this if our URL does not start with '/' because then
  // we could end up with a URL like:
  //   http://www.k5n.us/webcalendar/webcalendar/month.php
  if ( ! empty ( $SERVER_URL ) && substr ( $url, 0, 1 ) != '/' ) {
    $url = $SERVER_URL . $url;
  }

//echo "<pre>"; print_r ( debug_backtrace() ); echo "\n</pre>\n";
//echo "URL: $url <br>"; exit;

  $meta = '';
  if ( ( substr ( $SERVER_SOFTWARE, 0, 5 ) == 'Micro' ) ||
      ( substr ( $SERVER_SOFTWARE, 0, 3 ) == 'WN/' ) )
    $meta = '
    <meta http-equiv="refresh" content="0; url=' . $url . '" />';
  else
    header ( 'Location: ' . $url );

  echo send_doctype ( 'Redirect' ) . $meta . '
  </head>
  <body>
    Redirecting to.. <a href="' . $url . '">here</a>.
  </body>
</html>';
  dbi_close ( $c );
  exit;
}

/* Takes an input string and encode it into a slightly encoded hexval that we
 * can use as a session cookie.
 *
 * @param string $instr  Text to encode
 *
 * @return string  The encoded text.
 *
 * @global array Array of offsets
 *
 * @see decode_string
 */
function encode_string ( $instr ) {
  global $offsets;

  $cntOffsets = count ( $offsets );
  $ret = '';
  for ( $i = 0, $cnt = strlen ( $instr ); $i < $cnt; $i++ ) {
    $ret .= bin2hex ( chr ( ( ord ( substr ( $instr, $i, 1 ) ) + $offsets[ $i %
      $cntOffsets ] ) % 256 ) );
  }
  return $ret;
}

/* Check for errors and return required HTML for display
 *
 * @param string $nextURL   URL the redirect to
 * @param bool   $redirect  Redirect OR popup Confirmation window
 *
 * @return string  HTML to display.
 *
 * @global string  $error  Current error message
 *
 * @uses print_error_header
 */
function error_check ( $nextURL, $redirect = true ) {
  global $error;

  $ret = '';
  if ( ! empty ( $error ) ) {
    print_header ( '', '', '', true );
    $ret .= '
    <h2>' . print_error ( $error ) . '</h2>';
  } else {
    if ( $redirect )
      do_redirect ( $nextURL );

    $ret .= '<html>
  <head></head>
  <body onload="alert( \'' . translate ( 'Changes successfully saved', true )
     . '\' ); window.parent.location.href=\'' . $nextURL . '\';">';
  }
  return $ret . '
  </body>
</html>';
}

/* Gets the list of external users for an event from the
 * webcal_entry_ext_user table in HTML format.
 *
 * @param int $event_id    Event ID
 * @param int $use_mailto  When set to 1, email address will contain an href
 *                         link with a mailto URL.
 *
 * @return string  The list of external users for an event formated in HTML.
 */
function event_get_external_users ( $event_id, $use_mailto = 0 ) {
  $ret = '';

  $rows = dbi_get_cached_rows ( 'SELECT cal_fullname, cal_email
    FROM webcal_entry_ext_user WHERE cal_id = ? ORDER by cal_fullname',
    array ( $event_id ) );
  if ( $rows ) {
    for ( $i = 0, $cnt = count ( $rows ); $i < $cnt; $i++ ) {
      $row = $rows[$i];

      // Remove [\d] if duplicate name.
      $ret .= trim ( preg_replace ( '/\[[\d]]/', '', $row[0] ) );
      if ( strlen ( $row[1] ) ) {
        $row_one = htmlentities ( " <$row[1]>" );
        $ret .= ( $use_mailto
          ? ' <a href="mailto:' . "$row[1]\">$row_one</a>" : $row_one );
      }
      $ret .= "\n";
    }
  }
  return $ret;
}

/* Fakes an email for testing purposes.
 *
 * @param string $mailto  Email address to send mail to
 * @param string $subj    Subject of email
 * @param string $text    Email body
 * @param string $hdrs    Other email headers
 *
 * @ignore
 */
function fake_mail ( $mailto, $subj, $text, $hdrs ) {
  echo 'To: ' . $mailto . '<br />
Subject: ' . $subj . '<br />
' . nl2br ( $hdrs ) . '<br />
' . nl2br ( $text );
}

/* Generate activity log
 *
 *  @paran  int   $id       Event id if called from view_entry.php
 *  @param  bool  $sys      Display System Log ro Event Log
 *  @param  int   $startid  Event number to start off list
 *
 *  @return string  HTML to diplay log.
 */
function generate_activity_log ( $id = '', $sys = false, $startid = '' ) {
  global $GENERAL_USE_GMT, $nextpage, $PAGE_SIZE;

  $nextpage = '';
  $size = ( $id ? 'h3' : 'h2' );
  $sql_params = array ();
  if ( ! empty ( $id ) )
    $sql_params[] = $id;

  $sql_params[] = $startid;
  $ret = "<$size>"
   . ( $sys ? translate ( 'System Log' ) : translate ( 'Activity Log' ) )
   . ( $sys ? '' :
   ' &nbsp;<a href="rss_activity_log.php">' .
   '<img src="images/rss.png" width="14" height="14" alt="RSS 2.0 - ' .
   translate ( 'Activity Log' ) . '" border="0"/></a>' )
   . "</$size>" . display_admin_link () . '
    <table class="embactlog">
      <tr>
        <th class="usr">' . translate ( 'User' ) . '</th>
        <th class="cal">' . translate ( 'Calendar' ) . '</th>
        <th class="scheduled">' . translate ( 'Date' ) . '/'
   . translate ( 'Time' ) . '</th>' . ( $sys || $id ? '' : '
        <th class="dsc">' . translate ( 'Event' ) . '</th>' ) . '
        <th class="action">' . translate ( 'Action' ) . '</th>
      </tr>';

  $sql = 'SELECT wel.cal_login, wel.cal_user_cal, wel.cal_type, wel.cal_date,
    wel.cal_time, wel.cal_text, '
   . ( $sys
    ? 'wel.cal_log_id FROM webcal_entry_log wel WHERE wel.cal_entry_id = 0'
    : 'we.cal_id, we.cal_name, wel.cal_log_id, we.cal_type
      FROM webcal_entry_log wel, webcal_entry we
      WHERE wel.cal_entry_id = we.cal_id' )
   . ( empty ( $id ) ? '' : ' AND we.cal_id = ?' )
   . ( empty ( $startid ) ? '' : ' AND wel.cal_log_id <= ?' )
   . ' ORDER BY wel.cal_log_id DESC';

  $res = dbi_execute ( $sql, $sql_params );

  if ( $res ) {
    $num = 0;
    while ( $row = dbi_fetch_row ( $res ) ) {
      $l_login = $row[0];
      $l_user = $row[1];
      $l_type = $row[2];
      $l_date = $row[3];
      $l_time = $row[4];
      $l_text = $row[5];

      if ( $sys )
        $l_id = $row[6];
      else {
        $l_eid = $row[6];
        $l_ename = $row[7];
        $l_id = $row[8];
        $l_etype = $row[9];
      }
      $num++;
      if ( $num > $PAGE_SIZE ) {
        $nextpage = $l_id;
        break;
      } else
        $ret .= '
      <tr' . ( $num % 2 ? ' class="odd"' : '' ) . '>
        <td>' . $l_login . '</td>
        <td>' . $l_user . '</td>
        <td>' . date_to_str ( $l_date ) . '&nbsp;'
         . display_time ( $l_date . $l_time,
          // Added TZ conversion
          ( ! empty ( $GENERAL_USE_GMT ) && $GENERAL_USE_GMT == 'Y' ? 3 : 2 ) )
         . '</td>
        <td>' . ( ! $sys && ! $id ? '<a title="' . htmlspecialchars ( $l_ename )
           . '" href="view_entry.php?id=' . $l_eid . '">'
           . htmlspecialchars ( $l_ename ) . '</a></td>
        <td>' : '' ) . display_activity_log ( $l_type, $l_text ) . '</td>
      </tr>';
    }
    dbi_free_result ( $res );
  }

  return $ret . '
    </table>';
}

/* Generate Application Name
 *
 * @param bool $custom  Allow user name to be displayed
 */
function generate_application_name ( $custom = true ) {
  global $APPLICATION_NAME, $fullname;

  if ( empty ( $APPLICATION_NAME ) )
    $APPLICATION_NAME = 'Title';

  return ( $custom && ! empty ( $fullname ) && $APPLICATION_NAME == 'myname'
    ? $fullname
    : ( $APPLICATION_NAME == 'Title' || $APPLICATION_NAME == 'myname'
      ? ( function_exists ( 'translate' ) ? translate ( 'Title' ) : 'Title' )
      : htmlspecialchars ( $APPLICATION_NAME ) ) );
}

/* Generate HTML to add Printer Friendly Link.
 * If called without parameter, return only the href string.
 *
 * @param string $hrefin  script name
 *
 * @return string  URL to printer friendly page.
 *
 * @global array SERVER
 * @global string SCRIPT name
 * @global string (Y/N) Top menu enabled
 */
function generate_printer_friendly ( $hrefin = '' ) {
  global $_SERVER, $MENU_ENABLED, $SCRIPT, $show_printer;

  // Set this to enable printer icon in top menu.
  $href = ( empty ( $href ) ? $SCRIPT : $hrefin ) . '?'
   . ( empty ( $_SERVER['QUERY_STRING'] ) ? '' : addslashes(htmlentities($_SERVER['QUERY_STRING'])) );
  $href .= ( substr ( $href, -1 ) == '?' ? '' : '&' ) . 'friendly=1';
  $show_printer = true;
  if ( empty ( $hrefin ) ) // Menu will call this function without parameter.
    return $href;

  if ( $MENU_ENABLED == 'Y' ) // Return nothing if using menus.
    return '';

  $href = str_replace ( '&', '&amp;', $href );
  $displayStr = translate ( 'Printer Friendly' );
  $statusStr = translate ( 'Generate printer-friendly version' );

  return <<<EOT
    <a title="{$statusStr}" class="printer" href="{$href}"
      target="cal_printer_friendly">[{$displayStr}]</a>
EOT;
}

/* Generate Refresh Meta Tag.
 *
 * @return  HTML for Meta Tag.
 */
function generate_refresh_meta () {
  global $AUTO_REFRESH, $AUTO_REFRESH_TIME, $REQUEST_URI;

  return ( $AUTO_REFRESH == 'Y' && ! empty ( $AUTO_REFRESH_TIME ) && !
    empty ( $REQUEST_URI )
    ? '
    <meta http-equiv="refresh" content="'
     . $AUTO_REFRESH_TIME * 60 // Convert to seconds.
     . '; url=' . addslashes(htmlentities($REQUEST_URI)) . '" />' : '' );
}

/* Returns all the dates a specific event will fall on
 * accounting for the repeating.
 *
 * Any event with no end will be assigned one.
 *
 * @param int $date          Initial date in raw format
 * @param string $rpt_type   Repeating type as stored in the database
 * @param int $interval      Interval of repetition
 * @param array $Byxxx       Array of Byxxx values
 * @param int $Count         Max number of events to return
 * @param string $Until      Last day of repeat
 * @param string $Wkst       First day of week ('MO' is default)
 * @param array $ex_days     Array of exception dates for this event in YYYYMMDD format
 * @param array $inc_days    Array of inclusion dates for this event in YYYYMMDD format
 * @param int $jump          Date to short cycle loop counts to,
 *                           also makes output YYYYMMDD
 *
 * @return array  Array of dates (in UNIX time format).
 */
function get_all_dates ( $date, $rpt_type, $interval = 1, $Byxxx = '',
  $Count = 999, $Until = null, $Wkst = 'MO', $ex_days = '', $inc_days = '',
  $jump = '' ) {
  global $byday_names, $byday_values, $CONFLICT_REPEAT_MONTHS;

  $dateYmd = date ( 'Ymd', $date );
  $hour = date ( 'H', $date );
  $minute = date ( 'i', $date );

  if ( $Until == null && $Count == 999 ) {
    // Check for $CONFLICT_REPEAT_MONTHS months into future for conflicts.
    $thisyear = substr ( $dateYmd, 0, 4 );
    $thismonth = substr ( $dateYmd, 4, 2 ) + $CONFLICT_REPEAT_MONTHS;
    $thisday = substr ( $dateYmd, 6, 2 );
    if ( $thismonth > 12 ) {
      $thisyear++;
      $thismonth -= 12;
    }
    $realend = mktime ( $hour, $minute, 0, $thismonth, $thisday, $thisyear );
  } else
    $realend = ( $Count != 999
      ? mktime ( 0, 0, 0, 1, 1, 2038 ) // Set $until so some ridiculous value.
      : $Until );

  $ret = array ();
  $date_excluded = false; //Flag to track ical results.
  // Do iterative checking here.
  // I floored the $realend so I check it against the floored date.
  if ( $rpt_type && ( floor ( $date / 86400 ) * 86400 ) < $realend ) {
    $cdate = $date;
    $n = 0;
    $bymonth   = ( ! empty ( $Byxxx[0] ) ? explode ( ',', $Byxxx[0] ): array() );
    $byweekno  = ( ! empty ( $Byxxx[1] ) ? explode ( ',', $Byxxx[1] ): array() );
    $byyearday = ( ! empty ( $Byxxx[2] ) ? explode ( ',', $Byxxx[2] ): array() );
    $bymonthday= ( ! empty ( $Byxxx[3] ) ? explode ( ',', $Byxxx[3] ): array() );
    $byday     = ( ! empty ( $Byxxx[4] ) ? explode ( ',', $Byxxx[4] ): array() );
    $bysetpos  = ( ! empty ( $Byxxx[5] ) ? explode ( ',', $Byxxx[5] ): array() );

    if ( $rpt_type == 'daily' ) {
      // Skip to this year/month
      // if called from query_events and we don't need count.
      if ( ! empty ( $jump ) && $Count == 999 ) {
        while ( $cdate < $jump ) {
          $cdate = add_dstfree_time ( $cdate, 86400, $interval );
        }
      } while ( $cdate <= $realend && $n <= $Count ) {
        // Check RRULE items.
        if ( get_RRULE ( $date, $cdate, $Byxxx ) )
          $ret[$n++] = $cdate;

        $cdate = add_dstfree_time ( $cdate, 86400, $interval );
      }
    } elseif ( $rpt_type == 'weekly' ) {
      $r = 0;
      $dow = date ( 'w', $date );
      $cdate = $date - ( $dow * 86400 );
      if ( ! empty ( $jump ) && $Count == 999 ) {
        while ( ($cdate+604800) < $jump ) {
          $cdate = add_dstfree_time ( $cdate, 604800, $interval );
        }
      }
      while ( $cdate <= $realend && $n <= $Count ) {
        if ( ! empty ( $byday ) ) {
          $WkstDay = $byday_values[$Wkst];
          for ( $i=$WkstDay; $i<=( $WkstDay + 6 ); $i++ ) {
            $td = $cdate + ( $i * 86400 );
            $tdDay = date ( 'w', $td );
            //echo $Count . '  ' . $n . '  ' .$WkstDay .'<br>';
            if ( in_array ( $byday_names[$tdDay], $byday  ) && $td >= $date && $td <= $realend && $n <= $Count)
              $ret[$n++] = $td;
          }
        } else {
          $td = $cdate + ( $dow * 86400 );
          $cdow = date ( 'w', $td );
          if ( get_RRULE ( $date, $td, $Byxxx )
            && $cdow == $dow )
            $ret[$n++] = $td;
        }
        // Skip to the next week in question.
        $cdate = add_dstfree_time ( $cdate, 604800, $interval );
      }
    } elseif ( substr ( $rpt_type, 0, 7 ) == 'monthly' ) {
      $thisyear = substr ( $dateYmd, 0, 4 );
      $thismonth = substr ( $dateYmd, 4, 2 );
      $thisday = substr ( $dateYmd, 6, 2 );
      $hour = date ( 'H', $date );
      $minute = date ( 'i', $date );
      $cdate = mktime ( $hour, $minute, 0, $thismonth, $thisday, $thisyear );
      // Skip to this year if called from query_events and we don't need count.
      if ( ! empty ( $jump ) && $Count == 999 ) {
        while ( $cdate < $jump ) {
          $thismonth += $interval;
          $cdate = mktime ( $hour, $minute, 0, $thismonth, $thisday, $thisyear );
        }
      }
      $mdate = $cdate;
      while ( $cdate <= $realend && $n <= $Count ) {
        if ( ! getBymonth ( $cdate, $Byxxx[0] ) ) {
          //We skip this month
         } else {
          $bydayvalues = $bymonthdayvalues = $yret = array ();
          if ( count ( $byday ) )
            $bydayvalues = get_byday ( $byday, $mdate, 'month', $date );

          if ( count ( $bymonthday ) )
            $bymonthdayvalues = get_bymonthday ( $bymonthday, $mdate,
              $date, $realend );

          if ( count ( $byday ) && count ( $bymonthday ) ) {
            $bydaytemp = array_intersect ( $bymonthdayvalues, $bydayvalues );
            $yret = array_merge ( $yret, $bydaytemp );
          } elseif ( count ( $bymonthday ) )
            $yret = array_merge ( $yret, $bymonthdayvalues );
          elseif ( count ( $byday ) )
            $yret = array_merge ( $yret, $bydayvalues );
          elseif ( ! count ( $byday ) && ! count ( $bymonthday ) )
            $yret[] = $cdate;

          // Must wait till all other BYxx are processed.
          if ( count ( $bysetpos ) ) {
            $mth = date ( 'm', $cdate );
            sort ( $yret );
            sort ( $bysetpos );
            $setposdate = mktime ( $hour, $minute, 0, $mth, 1, $thisyear );
            $dim = date ( 't', $setposdate ); //Days in month.
            $yretcnt = count ( $yret );
            $bysetposcnt = count ( $bysetpos );
            for ( $i = 0; $i < $bysetposcnt; $i++ ) {
              if ( $bysetpos[$i] > 0 && $bysetpos[$i] <= $yretcnt )
                $ret[] = $yret[$bysetpos[$i] -1];
              else
              if ( abs ( $bysetpos[$i] ) <= $yretcnt )
                $ret[] = $yret[$yretcnt + $bysetpos[$i] ];
            }
          } else
          if ( ! empty ( $yret ) ) { // Add all BYxx additional dates.
            $yret = array_unique ( $yret );
            $ret = array_merge ( $ret, $yret );
          }
          sort ( $ret );
          $n = count ( $ret );
        }//end $bymonth test
        $thismonth += $interval;
        $cdate = mktime ( $hour, $minute, 0, $thismonth, $thisday, $thisyear );
        $mdate = mktime ( $hour, $minute, 0, $thismonth, 1, $thisyear );
      } //end while
    } elseif ( $rpt_type == 'yearly' ) {
      // This RRULE is VERY difficult to parse because RFC2445 doesn't
      // give any guidance on which BYxxx are mutually exclusive.
      // We will assume that:
      // BYMONTH, BYMONTHDAY, BYDAY go together.
      // BYDAY will be parsed relative to BYMONTH
      // if BYDAY is used without BYMONTH,
      // then it is relative to the current year (i.e 20MO).
      $thisyear = substr ( $dateYmd, 0, 4 );
      $thismonth = substr ( $dateYmd, 4, 2 );
      $thisday = substr ( $dateYmd, 6, 2 );
      $cdate = mktime ( $hour, $minute, 0, $thismonth, $thisday, $thisyear );
      // Skip to this year if called from query_events and we don't need count.
      if ( ! empty ( $jump ) && $Count == 999 ) {
        $jumpY = date ( 'Y', $jump );
        while ( date ( 'Y', $cdate ) < $jumpY ) {
          $thisyear += $interval;
          $cdate = mktime ( $hour, $minute, 0, $thismonth, $thisday, $thisyear );
        }
      }
      while ( $cdate <= $realend && $n <= $Count ) {
        $yret = array ();
        $ycd = date ( 'Y', $cdate );
        $fdoy = mktime ( 0, 0, 0, 1, 1, $ycd ); //first day of year
        $fdow = date ( 'w', $fdoy ); //day of week first day of year
        $ldoy = mktime ( 0, 0, 0, 12, 31, $ycd ); //last day of year
        $ldow = date ( 'w', $ldoy ); //day of week last day  of year
        $dow = date ( 'w', $cdate ); //day of week
        $week = date ( 'W', $cdate ); //ISO 8601 number of week
        if ( count ( $bymonth ) ) {
          foreach ( $bymonth as $month ) {
            $mdate = mktime ( $hour, $minute, 0, $month, 1, $ycd );
            $bydayvalues = $bymonthdayvalues = array ();
            if ( count ( $byday ) )
              $bydayvalues = get_byday ( $byday, $mdate, 'month', $date );

            if ( count ( $bymonthday ) )
              $bymonthdayvalues = get_bymonthday ( $bymonthday, $mdate,
                $date, $realend );

            if ( count ( $byday ) && count ( $bymonthday ) ) {
              $bydaytemp = array_intersect ( $bymonthdayvalues, $bydayvalues );
              $yret = array_merge ( $yret, $bydaytemp );
            } else
              $yret = ( count ( $bymonthday )
                ? array_merge ( $yret, $bymonthdayvalues )
                : ( count ( $byday )
                  ? array_merge ( $yret, $bydayvalues )
                  : array ( mktime ( $hour, $minute, 0, $month, $thisday, $ycd ) ) ) );
          } //end foreach bymonth
        } elseif ( count ( $byyearday ) ) { // end if isset bymonth
          foreach ( $byyearday as $yearday ) {
            preg_match( '/([-+]?)(\d{1,3})/', $yearday, $match );
            if ( $match[1] == '-' && ( $cdate >= $date ) )
              $yret[] =
              mktime ( $hour, $minute, 0, 12, 31 - $match[2] - 1, $thisyear );
            else
            if ( ( $n <= $Count ) && ( $cdate >= $date ) )
              $yret[] = mktime ( $hour, $minute, 0, 1, $match[2], $thisyear );
          }
        } elseif ( count ( $byweekno ) ) {
          $wkst_date = ( $Wkst == 'SU' ? $cdate + 86400 : $cdate );
          if ( count ( $byday ) )
            $bydayvalues = get_byday ( $byday, $cdate, 'year', $date );

          if ( in_array ( $week, $byweekno ) ) {
            if ( count ( $bydayvalues ) ) {
              foreach ( $bydayvalues as $bydayvalue ) {
                if ( $week == date ( 'W', $bydayvalue ) )
                  $yret[] = $bydayvalue;
              }
            } else
              $yret[] = $cdate;
          }
        } elseif ( count ( $byday ) ) {
          $bydayvalues = get_byday ( $byday, $cdate, 'year', $date );
          if ( ! empty ( $bydayvalues ) )
            $yret = array_merge ( $yret, $bydayvalues );
        } else // No Byxx rules apply.
          $ret[] = $cdate;

        // Must wait till all other BYxx are processed.
        if ( count ( $bysetpos ) ) {
          sort ( $yret );
          for ( $i = 0, $bysetposcnt = count ( $bysetpos ); $i < $bysetposcnt;
            $i++ ) {
            $ret[] = ( $bysetpos[$i] > 0
              ? $yret[$bysetpos[$i] -1]
              : $yret[count ( $yret ) + $bysetpos[$i] ] );
          }
        } else
        if ( ! empty ( $yret ) ) { // Add all BYxx additional dates.
          $yret = array_unique ( $yret );
          $ret = array_merge ( $ret, $yret );
        }
        sort ( $ret );
        $n = count ( $ret );
        $thisyear += $interval;
        $cdate = mktime ( $hour, $minute, 0, $thismonth, $thisday, $thisyear );
      }
    } //end if rpt_type
  }
  if ( ! empty ( $ex_days ) ) {
    foreach ( $ex_days as $ex_day ) {
      for ( $i = 0, $cnt = count ( $ret ); $i < $cnt;$i++ ) {
        if ( isset ( $ret[$i] ) &&
            date ( 'Ymd', $ret[$i] ) == substr ( $ex_day, 0, 8 ) )
          unset ( $ret[$i] );
      }
      // Remove any unset elements.
      sort ( $ret );
    }
  }
  if ( ! empty ( $inc_days ) ) {
    foreach ( $inc_days as $inc_day ) {
      $ret[] = strtotime ( $inc_day );
    }
  }
  // Remove any unset elements.
  sort ( $ret );
  // We want results