#!/usr/bin/perl
# ----------------------------------------------------------------------------
# This software is Copyright (C) 2007, All Rights Reserved
# ----------------------------------------------------------------------------
# This program is protected by local and international copyright laws. Any
# use of this program is subject to the the terms of the license agreement
# included as part of this distribution archive. Any other uses are strictly
# prohibited without the written permission of the Vendor and all
# other rights are reserved.
# -----------------------------------------------------------------------------
# Warning : Modifying this program violates your license agreement!
# -----------------------------------------------------------------------------

package IT::App::BrowseForDirOrFile;

use IT::Globals;

use strict;

# ------------------------------------------------------------------------
# Function    : browse
# Description : popup window to browse for files or dirs
#
# Usage       : &IT::App::BrowseForDirOrFile::browseFor($dirOrFile);
# ------------------------------------------------------------------------

sub browseFor {
  my $dirOrFile = ucfirst shift;
  my $template  = "admin/popups/browseFor$dirOrFile.html";

  # error checking
  if ($dirOrFile ne 'Dir' && $dirOrFile ne 'File') { &IT::Error::dieAsCaller("'dirOrFile' must be either 'Dir' or 'File'!"); }

  # define vars
  my $targetFieldNameAndID = $FORM->{'target_field_name_and_id'}  || die("no 'target_field_name_and_id' value specified");
  my $targetFieldValue     = $FORM->{'target_field_value'};
  my $newDir               = $FORM->{'dir'};
  my $createNewDir         = $FORM->{'createnewdir'};
  my $newDirectoryName     = $FORM->{'new_directory_name'};
  my $createNewFile        = $FORM->{'createnewfile'};
  my $newFileName          = $FORM->{'new_file_name'};

  ### get current browse dir
  my $currentBrowseDir;
  if    (-f $targetFieldValue)                { $currentBrowseDir = &IT::File::getParentDirectory($targetFieldValue); }  # convert filepath to dirpath
  elsif (-d $targetFieldValue)                { $currentBrowseDir = $targetFieldValue; }
  elsif ($newDir)                             { $currentBrowseDir = $newDir; } # user specified (browsed to) directory
  elsif (&_isUserAllowedToBrowseDir($CGIDIR)) { $currentBrowseDir = $CGIDIR; }
  else                                        { $currentBrowseDir = $SETTINGS->{'restricted_browsing_dir_ceiling'}; }
  $currentBrowseDir            = &IT::File::cleanPath( $currentBrowseDir ); # clean up path
  my $isUserAllowedToBrowseDir = &_isUserAllowedToBrowseDir($currentBrowseDir);

  ### Define placeholders

  ### restrict access for demos
  if (&IT::App::inDemoMode()) {

    if    ($createNewFile) { &IT::App::alert("Creating new files is disabled in demo mode."); }
    elsif ($createNewDir)  { &IT::App::alert("Creating new directories is disabled in demo mode."); }
    elsif ($newDir)        { &IT::App::alert("Directory browsing is disabled in demo mode."); }

    $currentBrowseDir = $CGIDIR;
    $createNewDir     = 0;
    $createNewFile    = 0;
  }


  ### Create new directory
  if ($createNewDir) {
    my $newDirPath = "$currentBrowseDir/$newDirectoryName";

    if    (!-d $currentBrowseDir)            { &IT::App::alert("The current path isn't a directory!\n"); }
    elsif (!$isUserAllowedToBrowseDir)       { &IT::App::alert("Creating directories in this directory isn't permitted!"); }
    elsif (!$newDirectoryName)               { &IT::App::alert("No directory name specified!"); }
    elsif ($newDirectoryName =~ m/[^\w\-]/i) { &IT::App::alert("Directory name may only contain A-Z, a-z, 0-9, - or _\n"); }
    else {
      unless (mkdir($newDirPath)) { &IT::App::alert("Unable to create dir '$newDirPath' : $!"); }
      $newDirectoryName= ""; # clear directory field
    }
  }

  ### Create new file
  if ($createNewFile) {
    my $newFilePath = "$currentBrowseDir/$newFileName";

    if    (!-d $currentBrowseDir)            { &IT::App::alert("The current path isn't a directory!"); }
    elsif (!$isUserAllowedToBrowseDir)       { &IT::App::alert("Creating files in this directory isn't permitted!"); }
    elsif (!$newFileName)                    { &IT::App::alert("No file name specified!"); }
    elsif ($newFileName =~ m/[^\w\-\.]/i)    { &IT::App::alert("File name may only contain A-Z, a-z, 0-9, -, _ or ."); }
    elsif (-f $newFilePath)                  { &IT::App::alert("File already exists!"); }
    else {
      &IT::FileLock::getLock('exclusive');
      unless (&IT::File::writeFile($newFilePath, '')) { &IT::App::alert("Unable to create file : $!\n"); }
      &IT::FileLock::releaseLock();
      $newFileName = ""; # clear filename field
    }
  }

  ### define placeholders
  &IT::Template::setGlobalPlaceholder('current_browse_dir',       $currentBrowseDir);
  &IT::Template::setGlobalPlaceholder('target_field_name_and_id', $targetFieldNameAndID);
  &IT::Template::setGlobalPlaceholder('new_directory_name',       $newDirectoryName);
  &IT::Template::setGlobalPlaceholder('new_file_name',            $newFileName);
  &IT::Template::setGlobalPlaceholder('DirOrFile',                $dirOrFile);
  &IT::App::setAlertWidthTo('narrow');
  &IT::Template::setGlobalPlaceholder('alerts',                   &IT::App::alert(''));

  ### add this last so it can use other global placeholders
  &IT::Template::setGlobalPlaceholder('list',                     &_generateDirectoryListHTML($currentBrowseDir, $dirOrFile));

  # display page
  my $placeholders = {};
  print &IT::Common::contentType('text/html');
  print &template($template, $placeholders);
  print &IT::App::debug() if $DEBUG_ENABLED;                                  # display debug messages
  exit;

}


# ----------------------------------------------------------------------------
# Function    : _generateDirectoryListHTML
# Description :
# Usage       : &_generateDirectoryListHTML($currentBrowseDir)
# ----------------------------------------------------------------------------

sub _generateDirectoryListHTML {
  my $currentBrowseDir = shift;
  my $DirOrFile        = ucfirst shift;
  my $list = '';
  my $template  = "admin/popups/browseFor$DirOrFile.html";

  ### Show Parent Dir Link
  my $parentDir         = &IT::File::getParentDirectory($currentBrowseDir);
  my $showParentDirLink = !&IT::File::isRootDir($currentBrowseDir) && &_isUserAllowedToBrowseDir($parentDir);
  if ($showParentDirLink) {
    my $placeholders = { 'parent_dir' => $parentDir };
    $list .= &templateCell($template, 'parent', $placeholders);
  }

  ### Show Program Dir Link
  if (&_isUserAllowedToBrowseDir($CGIDIR) && $currentBrowseDir ne $CGIDIR) {
    $list .= &templateCell($template, 'programdir', {});
  }

  ### Display Errors
  my $error = '';
  if    (-f $currentBrowseDir)                           { $error = "The specified path is a file not a directory!"; }
  elsif (!-d $currentBrowseDir)                          { $error = "The specified directory doesn't exist!"; }
  elsif (!-r $currentBrowseDir)                          { $error = "The specified directory isn't readable!"; }
  elsif (!&_isUserAllowedToBrowseDir($currentBrowseDir)) {
    my $link = "$CGIURL?action=browseFor$DirOrFile&dir=$SETTINGS->{'restricted_browsing_dir_ceiling'}&target_field_name_and_id=$FORM->{'target_field_name_and_id'}";
    $error   = "Directory browsing not allowed above <a href='$link'>$SETTINGS->{'restricted_browsing_dir_ceiling'}</a>";
  }

  if ($error) {
    $list .= &templateCell($template, 'error', { 'error' => $error });
    return $list;
  }

  # get file list
  my $dirHandle = &IT::File::createFilehandle();
  opendir($dirHandle, "$currentBrowseDir");
  my @dirFiles = sort { lc$a cmp lc$b } grep(!/^\.+$/, readdir($dirHandle));
  closedir($dirHandle);

  ### display dir files (and subdirs)
  my($fileListHTML, $dirListHTML);
  my $fileCount = 0;
  foreach my $fileOrDir (@dirFiles) {
    my $fullPath = "$currentBrowseDir/$fileOrDir";
    my($filesize, $lastModified) = (stat($fullPath))[7,9];
    my $lastModifiedLocalTime = &IT::App::Common::getTimeAdjustedToMatchGMT($lastModified || 1);  # default to epoch start if no date

    my $filesizeKB;
    if    ($filesize == 0) { $filesizeKB = 0; }
    else                   { $filesizeKB = int($filesize/1024) || 1; }

    my $rowPlaceholders = {};
    $rowPlaceholders->{'name'}             = $fileOrDir;
    $rowPlaceholders->{'fullpath'}         = $fullPath;
    $rowPlaceholders->{'sizeKB'}           = $filesizeKB;
    $rowPlaceholders->{'lastModifiedDate'} = &IT::Template::createDatePlaceholder( $lastModifiedLocalTime );

    if    (-d $fullPath) { $dirListHTML  .= &templateCell($template, 'dir', $rowPlaceholders); }
    elsif (-f $fullPath) { $fileListHTML .= &templateCell($template, 'file', $rowPlaceholders); }
    else                 { $fileListHTML .= &templateCell($template, 'other', $rowPlaceholders); }

    $fileCount++;
  }
  $list .= $dirListHTML;
  $list .= $fileListHTML;

  ### display "no files" if no files
  unless ($dirListHTML || $fileListHTML) {
    $list .= &templateCell($template, 'nofiles', {});
  }

  ### Add create new dir and file fields
  $list .= &templateCell($template, 'createnewdir', {});
  if ($DirOrFile eq 'File') {
    $list .= &templateCell($template, 'createnewfile', {});
  }

  return $list;
}

# ----------------------------------------------------------------------------
# Function    : _isUserAllowedToBrowseDir
# Description :
# Usage       : &_isUserAllowedToBrowseDir($dir)
# ----------------------------------------------------------------------------

sub _isUserAllowedToBrowseDir {
  my $currentdir = shift;

  # error checking
  unless (exists $SETTINGS->{'restrict_filesystem_browsing'})    { die "required settings field 'restrict_filesystem_browsing' doesn't exist!"; }
  unless (exists $SETTINGS->{'restricted_browsing_dir_ceiling'}) { die "required settings field 'restricted_browsing_dir_ceiling' doesn't exist!"; }

  # allow browsing anywhere if installing
  unless ($SETTINGS->{'isInstalled'}) { return 1; }

  # allow browsing anywhere if browsing isn't restricted
  unless ($SETTINGS->{'restrict_filesystem_browsing'}) { return 1; }

  # admins can browse anywhere regardless
  if ($CURRENT_USER->{'accessLevel'} >= 9) { return 1; }

  # otherwise, check if current dir is under dirCeiling
  $currentdir             = &IT::File::cleanPath( $currentdir );    # format dirs for pattern matching (removes ../ ./ etc)
  my $dirCeiling          = &IT::File::cleanPath( $SETTINGS->{'restricted_browsing_dir_ceiling'} );
  my $isChildOfCeilingDir = $currentdir =~ /^\Q$dirCeiling\E/;

  return $isChildOfCeilingDir;
}

# ----------------------------------------------------------------------------
# end of file
1;

