<?php

/**
 * @file
 * Cron relate functions.
 */

/**
 * Function to respond to ldap associated drupal accounts which no
 * longer have a related LDAP entry.
 *
 * @param LdapUserConf $ldap_user_conf
 *
 * @return boolean FALSE on error or incompletion or TRUE otherwise
 */
function _ldap_user_orphans($ldap_user_conf) {
  if (!$ldap_user_conf->orphanedDrupalAcctBehavior ||
    $ldap_user_conf->orphanedDrupalAcctBehavior == 'ldap_user_orphan_do_not_check') {
    return TRUE;
  }

  /**
   * query drupal accounts
   *   - ldap associated drupal accounts
   *   - where (ldap_user_current_dn not null)
   *   - ordered by ldap_user_last_checked
   *   - order by uid asc (get oldest first)
   */

  $last_uid_checked = variable_get('ldap_user_cron_last_uid_checked', 1);

  $query = new EntityFieldQuery();
  $query->entityCondition('entity_type', 'user')
    ->fieldCondition('ldap_user_puid_sid', 'value', 'NULL', '!=')
    ->fieldCondition('ldap_user_puid_property', 'value', 'NULL', '!=')
    ->fieldCondition('ldap_user_puid', 'value', 'NULL', '!=')
    ->fieldCondition('ldap_user_current_dn', 'value', 'NULL', '!=')
    ->propertyCondition('uid', $last_uid_checked, '>')
    ->propertyCondition('status', 1)
    ->propertyOrderBy('uid', 'ASC')
    ->range(0, $ldap_user_conf->orphanedCheckQty)
  // Run the query as user 1.
    ->addMetaData('account', user_load(1));
  $result = $query->execute();

  $email_list = [];
  $ldap_servers = ldap_servers_get_servers(NULL, 'enabled');
  $watchdogs_sids_missing_watchdogged = [];
  /**
   * First produce array of form:
   *  $drupal_users[$sid][$puid_attr][$puid]['exists'] = bool
   *  signifying if corresponding LDAP Entry exists.
   */
  if (!(isset($result['user']) && count($result['user']) > 0)) {
    variable_set('ldap_user_cron_last_uid_checked', 1);
    return TRUE;
  }

  $uids = array_keys($result['user']);
  $user_count = count($uids);

  // If maxed out reset uid check counter.
  if ($user_count < $ldap_user_conf->orphanedCheckQty) {
    variable_set('ldap_user_cron_last_uid_checked', 1);
  }
  else {
    variable_set('ldap_user_cron_last_uid_checked', $uids[count($uids) - 1]);
  }

  $batches = floor($user_count / LDAP_SERVERS_MAXFILTER_ORS) + 1;
  // e.g. 175 users and  50 max ldap query ors will yield 4 batches.
  for ($batch = 1; $batch <= $batches; $batch++) {
    $email_list = _ldap_user_orphan_process_batch($ldap_user_conf, $batch, $user_count, $uids, $ldap_servers, $watchdogs_sids_missing_watchdogged, $query, $email_list);
  }

  if (count($email_list) > 0) {
    $site_email = variable_get('site_mail', FALSE);
    $params = ['accounts' => $email_list];
    if ($site_email) {
      drupal_mail(
        'ldap_user',
        'orphaned_accounts',
        $site_email,
        language_default(),
        $params
      );
    }
  }

  return TRUE;
}

/**
 * @param $ldap_user_conf
 * @param $batch
 * @param $user_count
 * @param $uids
 * @param $ldap_servers
 * @param $watchdogs_sids_missing_watchdogged
 * @param $query
 * @param $email_list
 *
 * @return array
 * @throws \Exception
 */
function _ldap_user_orphan_process_batch($ldap_user_conf, $batch, $user_count, $uids, $ldap_servers, $watchdogs_sids_missing_watchdogged, $query, $email_list) {
  $filters = [];
  $drupal_users = [];
  /**
   * 1. populate $drupal_users[$sid][$puid_attr][$puid]['exists']  = TRUE
   *
   * e.g.  first batch $i=0; $i<50; $i++
   *       2nd batch   $i=50; $i<100; $i++
   *       4th batch   $i=150; $i<175; $i++
   */
  // e.g 0, 50, 100.
  $start = ($batch - 1) * LDAP_SERVERS_MAXFILTER_ORS;
  // e.g. 50, 100, 150.
  $end_plus_1 = min(($batch) * LDAP_SERVERS_MAXFILTER_ORS, $user_count);
  // e.g. 50, 50; 100, 50.
  $batch_uids = array_slice($uids, $start, ($end_plus_1 - $start));
  $accounts = entity_load('user', $batch_uids);

  foreach ($accounts as $uid => $user) {
    $sid = @$user->ldap_user_puid_sid[LANGUAGE_NONE][0]['value'];
    $puid = @$user->ldap_user_puid[LANGUAGE_NONE][0]['value'];
    $puid_attr = @$user->ldap_user_puid_property[LANGUAGE_NONE][0]['value'];
    if ($sid && $puid && $puid_attr) {
      if ($ldap_servers[$sid]->unique_persistent_attr_binary) {
        $filters[$sid][$puid_attr][] = "($puid_attr=" . ldap_servers_binary_filter($puid) . ")";
      }
      else {
        $filters[$sid][$puid_attr][] = "($puid_attr=$puid)";
      }
      $drupal_users[$sid][$puid_attr][$puid]['uid'] = $uid;
      $drupal_users[$sid][$puid_attr][$puid]['exists'] = FALSE;
    }
    else {
      // User with missing ldap data fields
      // perhaps should be watchdogged?
    }
  }

  // 2. set $drupal_users[$sid][$puid_attr][$puid]['exists'] to FALSE
  // if entry doesn't exist.
  foreach ($filters as $sid => $puid_attrs) {
    if (!isset($ldap_servers[$sid])) {
      if (!isset($watchdogs_sids_missing_watchdogged[$sid])) {
        watchdog(
          'ldap_user',
          'Server %sid not enabled, but needed to remove orphaned ldap users',
          ['%sid' => $sid],
          WATCHDOG_ERROR
        );
        $watchdogs_sids_missing_watchdogged[$sid] = TRUE;
      }
      continue;
    }
    foreach ($puid_attrs as $puid_attr => $ors) {
      // Query should look like (|(guid=3243243)(guid=3243243)(guid=3243243))
      $ldap_filter = '(|' . join("", $ors) . ')';
      $ldap_entries = $ldap_servers[$sid]->searchAllBaseDns($ldap_filter, [$puid_attr]);
      if ($ldap_entries === FALSE) {
        // If query has error, don't remove ldap entries!
        unset($drupal_users[$sid]);
        watchdog(
          'ldap_user',
          'ldap server %sid had error while querying to
            deal with orphaned ldap user entries.  Please check that the ldap
            server is configured correctly.  Query; %query',
          ['%sid' => $sid, '%query' => serialize($query)],
          WATCHDOG_ERROR);
        continue;
      }

      unset($ldap_entries['count']);

      foreach ($ldap_entries as $i => $ldap_entry) {
        $puid = $ldap_servers[$sid]->userPuidFromLdapEntry($ldap_entry);
        $drupal_users[$sid][$puid_attr][$puid]['exists'] = TRUE;
      }
    }
  }
  // 3. we now have $drupal_users[$sid][$puid_attr][$puid]['exists'] = bool.
  global $base_url;
  foreach ($drupal_users as $sid => $puid_x_puid_attrs) {
    foreach ($puid_x_puid_attrs as $puid_attr => $puids) {
      foreach ($puids as $puid => $user_data) {

        if ($account = $accounts[$user_data['uid']]) {
          $user_edit = [];
          $user_edit['ldap_user_last_checked'][LANGUAGE_NONE][0]['value'] = time();
          $account = user_save($account, $user_edit, 'ldap_user');
          if (!$user_data['exists']) {
            /**
             * $ldap_user_conf->orphanedDrupalAcctBehavior will either be
             *  'ldap_user_orphan_email' or one of the user module options:
             *     user_cancel_block, user_cancel_block_unpublish,
             *     user_cancel_reassign, user_cancel_delete
             */
            if ($ldap_user_conf->orphanedDrupalAcctBehavior == 'ldap_user_orphan_email') {
              $email_list[] = $account->name . "," . $account->mail . "," . $base_url . "/user/" . $account->uid . "/edit";
            }
            else {
              _user_cancel([], $account, $ldap_user_conf->orphanedDrupalAcctBehavior);
            }
          }
        }
      }
    }
  }
  return $email_list;
}
