<?php
/*******************************************************************************
 * Copyright (C) 2007 Easter-eggs
 * https://ldapsaisie.org
 *
 * Author: See AUTHORS file in top-level directory.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

******************************************************************************/

// Error messages

// Support
LSerror :: defineError('LSORGCHART_SUPPORT_01',
  ___("Organizational Chart Support : The global array %{array} is not defined.")
);
LSerror :: defineError('LSORGCHART_SUPPORT_02',
  ___("Organizational Chart Support : The global variable %{var} is not defined.")
);

/**
 * Check support of orgchart addon by LdapSaisie
 *
 * @author Benjamin Renard <brenard@easter-eggs.com>
 *
 * @return boolean true if orgchart addon is totally supported, false in other case
 */
function LSaddon_orgchart_support() {
  $retval = True;
  $MUST_DEFINE_ARRAY = array(
    'ORGCHART_ENTITY_OBJECT_TYPES',
    'ORGCHART_ADDITIONAL_CSS_FILES',
    'ORGCHART_ALLOWED_PROFILES',
  );
  foreach($MUST_DEFINE_ARRAY as $array) {
    if (!isset($GLOBALS[$array]) || !is_array($GLOBALS[$array])) {
      LSerror :: addErrorCode('LSORGCHART_SUPPORT_01', $array);
      $retval = false;
    }
  }
  $MUST_DEFINE_VAR = array(
    'ORGCHART_TEMPLATE',
  );
  foreach($MUST_DEFINE_VAR as $var) {
    if (!isset($GLOBALS[$var])) {
      LSerror :: addErrorCode('LSORGCHART_SUPPORT_02', $var);
      $retval = false;
    }
  }

  if ($retval) {
    $retval = (
      LSsession :: registerLSaddonView(
        'orgchart',
        'orgchart',
        _('Organizational chart'),
        'organizationalChartPage',
        ($GLOBALS['ORGCHART_ALLOWED_PROFILES']?$GLOBALS['ORGCHART_ALLOWED_PROFILES']:null),
        true  // Show in menu
      ) &&
      LSsession :: registerLSaddonView(
        'orgchart',
        'orgchartdata',
        _('Organizational chart data'),
        'organizationalChartData',
        ($GLOBALS['ORGCHART_ALLOWED_PROFILES']?$GLOBALS['ORGCHART_ALLOWED_PROFILES']:null),
        false  // Show in menu
      )
    );
  }

  return $retval;
}

function organizationalChartPage() {
  LStemplate :: assign('template', $GLOBALS['ORGCHART_TEMPLATE']);
  LStemplate :: assign('additional_css_files', $GLOBALS['ORGCHART_ADDITIONAL_CSS_FILES']);
  LSsession :: setTemplate('organizationalChart.tpl');
}

function organizationalChartData() {
  $logger = LSlog :: get_logger('LSaddon_orgchart');
  $root_entity = (isset($GLOBALS['ORGCHART_ROOT_ENTITY'])?$GLOBALS['ORGCHART_ROOT_ENTITY']:null);

  $objects = array();
  $objects_conf = array();
  $objects_attr2dn = array();
  $objects_attr2dn_need = array();
  $requested_attrs = array();

  $logger -> trace(
    "Orgchart entity object types configuration:\n".
    print_r($GLOBALS['ORGCHART_ENTITY_OBJECT_TYPES'], 1));

  // Load all object types
  foreach($GLOBALS['ORGCHART_ENTITY_OBJECT_TYPES'] as $obj_type => $conf) {
    if (!LSsession :: loadLSobject($obj_type))
      $logger -> fatal("Fail to load object type '$obj_type'.");
    $objects[$obj_type] = array();
    $requested_attrs[$obj_type] = array();
    $objects_attr2dn[$obj_type] = array();
    $objects_attr2dn_need[$obj_type] = array();
    $objects_conf[$obj_type] = array();
  }

  // Load configuration and attributes to request for each object types
  foreach($GLOBALS['ORGCHART_ENTITY_OBJECT_TYPES'] as $obj_type => $conf) {
    $parent_id_attr = LSconfig :: get('parent_id_attr', '', 'string', $conf);
    if ($parent_id_attr) {
      if (!$obj_type :: hasAttr($parent_id_attr))
        $logger -> fatal("Object '$obj_type' does not have attribute '$parent_id_attr'.");
      $requested_attrs[$obj_type][] = $parent_id_attr;
    }
    $objects_conf[$obj_type]['parent_id_attr'] = $parent_id_attr;

    $objects_conf[$obj_type]['other_attrs'] = LSconfig :: get('other_attrs', array(), 'array', $conf);
    foreach($objects_conf[$obj_type]['other_attrs'] as $attr) {
      if ($obj_type :: hasAttr($attr)) {
        if (!in_array($attr, $requested_attrs))
          $requested_attrs[$obj_type][] = $attr;
      }

      foreach(getFieldInFormat($attr) as $a) {
        if (!$obj_type :: hasAttr($a))
          $logger -> fatal("Object '$obj_type' does not have attribute '$a'.");
        if (!in_array($a, $requested_attrs[$obj_type]))
          $requested_attrs[$obj_type][] = $a;
      }
    }

    $parent_id_attr_value = LSconfig :: get('parent_id_attr_value', 'dn', 'string', $conf);
    $objects_conf[$obj_type]['parent_id_attr_value'] = $parent_id_attr_value;

    $objects_conf[$obj_type]['parent_object_types'] = LSconfig :: get(
      'parent_object_types',
      array_keys($GLOBALS['ORGCHART_ENTITY_OBJECT_TYPES']),
      'array', $conf);
    foreach($objects_conf[$obj_type]['parent_object_types'] as $parent_object_type) {
      if (!array_key_exists($parent_object_type, $objects))
        $logger -> fatal("Object type '$parent_object_type' not configured for the organizational chart.");

      if ($parent_id_attr_value == 'dn') continue;

      if (!$parent_object_type :: hasAttr($parent_id_attr_value))
        $logger -> fatal("Object '$parent_object_type' does not have attribute '$parent_id_attr_value'.");

      if (!in_array($parent_id_attr_value, $requested_attrs[$parent_object_type]))
        $requested_attrs[$parent_object_type][] = $parent_id_attr_value;

      if (!in_array($parent_id_attr_value, $objects_attr2dn_need[$parent_object_type])) {
        $objects_attr2dn_need[$parent_object_type][] = $parent_id_attr_value;
        $objects_attr2dn[$parent_object_type][$parent_id_attr_value] = array();
      }
    }

    // Load optional filter
    $objects_conf[$obj_type]['filter'] = LSconfig :: get('filter', '', 'string', $conf);
  }
  $logger -> trace("Objects types configuration:\n".print_r($objects_conf, 1));

  // Load all objects
  if (!LSsession :: loadLSclass('LSsearch'))
    $logger -> fatal("Error loading LSsearch class");
  foreach($objects_conf as $obj_type => $conf) {
    // Compute search parameters
    $search_params = array(
      'attributes' => $requested_attrs[$obj_type],
    );
    if ($conf['filter']) {
      $search_params['filter'] = $conf['filter'];
    }

    // Run search
    $search = new LSsearch($obj_type, 'orgchart', $search_params);
    $search -> run();

    // Iter on found entries and store object data in $objects[$obj_type]
    foreach($search -> listEntries() as $dn => $entry) {
      $data = array(
        'type' => $obj_type,
        'url' => "object/$obj_type/".urlencode($dn),
        'parent_id' => null,
      );

      // Load parent ID
      if ($conf['parent_id_attr']) {
        $parent_ids = ensureIsArray($entry->get($conf['parent_id_attr']));
        if ($parent_ids) $data['parent_id'] = $parent_ids[0];
      }

      // Load other attributes values
      foreach ($conf['other_attrs'] as $key => $attr)
        $data[$key] = ensureIsArray($entry->get($attr));
      $objects[$obj_type][$dn] = $data;

      // If some object types refer to this one using attribute values,
      // store attribute values to DN in $objects_attr2dn[$obj_type][$attr]
      foreach($objects_attr2dn_need[$obj_type] as $attr)
        foreach(ensureIsArray($entry->get($attr)) as $value)
          $objects_attr2dn[$obj_type][$attr][$value] = $dn;
    }
  }
  $logger -> trace("Loaded object:\n".print_r($objects, 1));
  $logger -> trace("Loaded attr2dn:\n".print_r($objects_attr2dn, 1));

  // Compute list of org chart entities
  $entities = array();
  $root_entities = array();
  foreach($objects_conf as $obj_type => $conf) {
    foreach($objects[$obj_type] as $dn => $data) {
      // Check parent exist
      $data['parentId'] = null;
      if ($conf['parent_id_attr'] && $data['parent_id']) {
        // Object have a parent
        $parent_id = $data['parent_id'];

        if ($conf['parent_id_attr_value'] == 'dn') {
          // Parent is refer by DN
          foreach($conf['parent_object_types'] as $parent_object_type) {
            if (array_key_exists($parent_id, $objects[$parent_object_type])) {
              $data['parentId'] = $parent_id;
              break;
            }
          }
        }
        else {
          // Parent is refer by one of its attribute value
          foreach($conf['parent_object_types'] as $parent_object_type) {
            if (array_key_exists($parent_id, $objects_attr2dn[$parent_object_type][$conf['parent_id_attr_value']])) {
              $data['parentId'] = $objects_attr2dn[$parent_object_type][$conf['parent_id_attr_value']][$parent_id];
              break;
            }
          }
        }

        if (is_null($data['parentId'])) {
          $logger -> warning(sprintf('%s: Parent "%s" not found: use root entity if defined or ignored object', $dn, $data['parent_id']));
          if ($root_entity) {
            $data['parentId'] = $root_entity['id'];
	  }
          else {
            // No root entity: do not add this object from chart
            $logger -> warning(sprintf('%s: No root entity: do not add this object from chart', $dn));
            continue;
	  }
        }
      }
      elseif ($root_entity && !$data['parent_id']) {
        // Object have no parent, but root entity configured : add it
        $data['parentId'] = $root_entity['id'];
      }
      elseif (!$root_entity && !$data['parent_id']) {
        // Object have no parent and no root entity configured : store it as root entities
        $root_entities[] = $dn;
      }
      // Remove parent_id and keep only parentId key
      unset($data['parent_id']);

      // Store DN as chart entity ID
      $data['id'] = $dn;
      $entities[] = $data;
    }
  }

  if ($root_entity) {
    // Root entity configured : add it to chart entities
    $entities[] = $root_entity;
  }
  elseif(count($root_entities) != 1) {
    $logger -> fatal('More than one root entities found : '.implode(' / ', $root_entities));
  }

  // Adjust content-type and dump entities data as JSON
  header('Content-Type: application/json');
  echo json_encode(
    $entities,
    (
      isset($_REQUEST['pretty'])?
      JSON_PRETTY_PRINT:
      0
    )
  );
  exit();
}
