<?php
/**
 * Directory administration code.
 *
 * @author Jan Dittberner <jan@dittberner.info>
 * @version $Id$
 * @license GPL
 * @package DAVAdmin
 *
 * Copyright (c) 2007, 2008 Jan Dittberner
 *
 * This file is part of DAVAdmin.
 *
 * DAVAdmin 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 3 of the License, or
 * (at your option) any later version.
 *
 * DAVAdmin is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with DAVAdmin; if not, see <http://www.gnu.org/licenses/>.
 */

/** Include common code. */
include_once('common.inc.php');

/** Format string for group lines in .htaccess files. */
define('GROUPLINEFORMAT', "require group %s\n");

/** Regular expression for finding group lines in .htaccess files. */
define('GROUPLINERE', '/^require\s+group\s+(.*)$/i');

/** Regular expression matching legal directory names. */
define('DIRNAMERE', '/^[a-zA-Z0-9 -_.]+$/');

/**
 * List of mandatory groups. These groups are assigned to every new or
 * changed directory.
 */
$mandatorygroups = array(ADMIN_GROUP);

/**
 * Gets the group names allowed to access the given directory from its
 * .htaccess file. Mandatory groups are excluded.
 *
 * @param string $dirname a fully qualified directory name
 * @return array an array of group names
 */
function getDirGroupsFromHtaccess($dirname) {
  $htaccessname = getFullPath($dirname) . DIRECTORY_SEPARATOR . ".htaccess";
  $groups = array();
  if (false !== ($fh = fopen($htaccessname, "r"))) {
    while (!feof($fh)) {
      $buffer = fgets($fh);
      if (preg_match_all(GROUPLINERE, trim($buffer), $matches)) {
        $grouparr = explode(" ", $matches[1][0]);
        foreach ($grouparr as $group) {
          $group = trim($group);
          if (!in_array($group, $GLOBALS['mandatorygroups']) &&
              !in_array($group, $groups)) {
            array_push($groups, $group);
          }
        }
      }
    }
  }
  fclose($fh);
  return $groups;
}

/**
 * Counts the visible files and their accumulated size in a directory
 * tree.
 *
 * @param string $dirname a fully qualified directory name
 * @param array $retval a two component array index 0 is the file
 * count, index 1 is the accumulated size of files
 * @return array array updated with the data from subdirectories and
 * files of $dirname
 */
function countFilesRecursive($dirname, $retval = array(0, 0)) {  
  $dh = opendir($dirname);
  while (false !== ($entry = readdir($dh))) {
    $fullname = $dirname . DIRECTORY_SEPARATOR . $entry;
    if (strpos($entry, '.') !== 0) {
      if (is_dir($fullname)) {
        $retval = countFilesRecursive($fullname, $retval);
      } else if (is_file($fullname)) {
        $retval = array($retval[0] + 1, $retval[1] + filesize($fullname));
      }
    }
  }
  closedir($dh);
  return $retval;
}

/**
 * Gets the data of a directory.
 *
 * @param string $dirname a fully qualified directory name
 * @return array array containing directory data
 */
function getDirectoryData($dirname) {
  $dir = array();
  $dir['name'] = basename($dirname);
  $dir['groups'] = getDirGroupsFromHtaccess($dirname);
  list($dir['filecount'], $dir['filesize']) = countFilesRecursive(getFullPath($dirname));
  $dir['maydelete'] = ($dir['filecount'] == 0) ? 1 : 0;
  $dir['filesize'] = sprintf(_("%d kBytes"), $dir['filesize'] / 1024);
  return $dir;
} 

/**
 * Gets XML encoded data of a directory.
 *
 * @param string $dirname dirname relative to {@link $davconfig['dav.dir']}
 * @return string XML string
 */
function getDirectoryDataAsXml($dirname) {
  if (is_dir(getFullPath($dirname))) {
    $dirdata = getDirectoryData($dirname);
    header("Content-Type: text/xml; charset=UTF-8");
    return sprintf('<?xml version="1.0" encoding="utf8"?><directory><dirname>%s</dirname><groups>%s</groups><filecount>%d</filecount><filesize>%s</filesize><maydelete>%d</maydelete></directory>', $dirdata['name'], implode(", ", $dirdata['groups']), $dirdata['filecount'], $dirdata['filesize'], $dirdata['maydelete']);
  } else {
    errorAsXml(sprintf(_("Invalid directory name %s!"), $dirname));
  }
}

/**
 * Gets XML encoded data of a deleted directory.
 *
 * @param string $dirname directory name relative to $davconfig['dav.dir']
 * @return string XML string
 */
function getDeletedDirectoryData($dirname) {
  header("Content-Type: text/xml; charset=UTF-8");
  return sprintf('<?xml version="1.0" encoding="utf8"?><directory><dirname>%s</dirname></directory>', $dirname);
}

/**
 * Gets the list of directory data for all valid directories below
 * {@link $davconfig['dav.dir']}.
 *
 * @return array array of directory data arrays
 * @see #getDirectoryData(string)
 */
function getDirectories() {
  $dirs = array();
  if (false !== ($entries = scandir($GLOBALS['davconfig']['dav.dir']))) {
    foreach ($entries as $entry) {
      if (is_dir(getFullPath($entry))) {
        if (strpos($entry, '.') !== 0) {
          if ($entry != ADMIN_DIR) {
            array_push($dirs, getDirectoryData($entry));
          }
        }
      }
    }
  }
  return $dirs;
}

/**
 * Sets the groups of a directory in its .htaccess file. Mandatory
 * groups are added automatically.
 *
 * @param string $dirname directory name relative to {@link $davconfig['dav.dir']}
 * @param array &$groups reference to a list of group names
 */
function setGroups($dirname, &$groups) {
  $fullname = getFullPath($dirname);
  foreach ($groups as $key => $value) {
    $groups[$key] = trim($value);
  }
  foreach ($GLOBALS['mandatorygroups'] as $mgroup) {
    if (!in_array($mgroup, $groups)) {
      array_push($groups, $mgroup);
    }
  }
  $groups = array_unique($groups);
  asort($groups);
  $found = false;
  $lines = array();
  $found = false;
  if (is_dir($fullname)) {
    $htaccess = $fullname . DIRECTORY_SEPARATOR . ".htaccess";
    if (file_exists($htaccess) && (false !== ($fh = fopen($htaccess, "r")))) {
      while (!feof($fh)) {
        $buffer = fgets($fh);
        if (preg_match(GROUPLINERE, $buffer)) {
          array_push($lines, sprintf(GROUPLINEFORMAT, join(" ", $groups)));
          $found = true;
        } else {
          array_push($lines, $buffer);
        }
      }
      fclose($fh);
    }
    if (!$found) {
      array_push($lines, sprintf(GROUPLINEFORMAT, join(" ", $groups)));
    }
    if (false !== ($fh = fopen($htaccess, "w"))) {
      foreach ($lines as $line) {
        fwrite($fh, $line);
      }
      fclose($fh);
    }
  }
}

/**
 * Updates a directory to be accessible by the given list of
 * groups. The directory is created if it doesn't exist.
 *
 * @param string $dirname directory name relative to $davconfig['dav.dir']
 * @param array $groups a list of group names
 */
function updateDirectory($dirname, $groups) {
  if (preg_match(DIRNAMERE, $dirname, $matches)) {
    if ($dirname != ADMIN_DIR) {
      $fullname = getFullPath($dirname);
      if (file_exists($fullname)) {
        if (!is_dir($fullname)) {
          errorAsXml(sprintf(_("There already is a directory entry named %s, but it's not a directory!"), $dirname));
        }
      } else {
        mkdir($fullname);
      }
      setGroups($dirname, $groups);
    }
    return;
  }
  errorAsXml(sprintf(_("Invalid directory name %s!"), $dirname));
}

/**
 * Recursively deletes the given directory.
 *
 * @param string $fullname fully qualified directory name
 */
function delrecursive($fullname) {
  $dh = opendir($fullname);
  while (false !== ($entry = readdir($dh))) {
    if ($entry != "." && $entry != "..") {
      $entryname = $fullname . DIRECTORY_SEPARATOR . $entry;
      if (is_dir($entryname)) {
        delrecursive($entryname);
      } else {
        unlink($entryname);
      }
    }
  }
  closedir($dh);
  rmdir($fullname);
  return true;
}

/**
 * Deletes the given directory if it has a valid name and is not the
 * administration interface directory.
 *
 * @param string $dirname directory name relative to $davconfig['dav.dir']
 */
function deleteDirectory($dirname) {
  global $davconfig;
  if (preg_match(DIRNAMERE, $dirname, $matches)) {
    $fullname = $davconfig['dav.dir'] . DIRECTORY_SEPARATOR . $dirname;
    if (is_dir($fullname)) {
      return delrecursive($fullname);
    }
  }
  errorAsXml(sprintf(_("Invalid directory name %s!"), $dirname));
}

if ($_GET) {
  // handle get requests with data
  if ($_GET['method']) {
    switch ($_GET['method']) {
    case 'getdirectorydata':
      print getDirectoryDataAsXml($_GET['dirname']);
      break;
    default:
      errorAsXml(sprintf(_("Unexpected values %s!"), serialize($_GET)));
    }
  } else if (isset($_GET["language"])) {
    header("Content-Type: text/html; charset=UTF-8");
    $smarty->assign("directories", getDirectories());
    $smarty->display("directories.html");
  } else {
    invalidCall();
  }
} elseif ($_POST) {
  // handle post requests with data
  if ($_POST['method']) {
    switch ($_POST['method']) {
    case 'submitdirectory':
      updateDirectory($_POST['dirname'], explode(",", $_POST['groups']));
      print getDirectoryDataAsXml($_POST['dirname']);
      break;
    case 'deletedirectory':
      if (deleteDirectory($_POST['dirname'])) {
        print getDeletedDirectoryData($_POST['dirname']);
      } else {
        errorAsXml(_("Delete failed!"));
      }
      break;
    default:
      errorAsXml(sprintf(_("Unexpected values %s!"), serialize($_POST)));
    }
  } else {
    invalidCall();
  }
} else {
  // handle requests without data
  header("Content-Type: text/html; charset=UTF-8");
  $smarty->assign("directories", getDirectories());
  $smarty->display("directories.html");
}
?>