<?php
/**
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Etienne MELEARD <etienne.meleard@free.fr>
 */

// must be run within Dokuwiki
if(!defined('DOKU_INC')) die();

class helper_plugin_aclauditor extends DokuWiki_Plugin {
	var $acls = null;
	var $usersgroups = null;
	
	function getInfo() {
		return array(
			'author' => 'Etienne MELEARD',
			'email'  => 'etienne.meleard@free.fr',
			'date'   => @file_get_contents(DOKU_PLUGIN.'aclauditor/VERSION'),
			'name'   => 'ACL Auditor Plugin (helper class)',
			'desc'   => 'Functions to get info about user /group ACLs of a wiki page',
			'url'    => 'http://dokuwiki.org/plugin:aclauditor',
		);
	}
	
	function getMethods() {
		return array(
			array(
				'name'   => 'resourceACLs',
				'desc'   => 'returns the list of ACLs that are applied on a resource (page/media)',
				'params' => array(
					'resource' => 'string'
				),
				'return' => array(
					'list' => 'array'
				)
			),
			array(
				'name'   => 'getACL',
				'desc'   => 'returns the list of ACLs for a user/group',
				'params' => array(
					'of' => 'string'
				),
				'return' => array(
					'list' => 'array'
				)
			),
			array(
				'name'   => 'getACLon',
				'desc'   => 'returns the list of ACLs that are applied on a resource (page/media) for a user or a group',
				'params' => array(
					'of' => 'string',
					'resource' => 'string'
				),
				'return' => array(
					'list' => 'array'
				)
			)
		);
	}
	
	/**
	 * Gets ACLs applied on a resource
	 * @param $resource string or array of page id or media id to get acl list about
	 * @retrun array
	 */
	function resourceACLs($resource, $locale = false) {
		$this->init();
		
		$acls = array();
		
		foreach($this->usersgroups as $u => $grps) if(auth_isadmin($u, $grps)) {
			$acls[$u] = array();
			$acls[$u]['ADMIN'] = array('what' => '*', 'who' => $u, 'rule' => 'ADMIN', 'lvl' => AUTH_ADMIN);
		}
		$r = $this->IDPathTree($resource);
		foreach($this->acls as $w => $parts) {
			if(!in_array($parts['what'], $r)) continue;
			if(!isset($acls[$parts['who']])) $acls[$parts['who']] = array();
			$acls[$parts['who']][$w] = array('what' => $parts['what'], 'who' => $parts['who'], 'rule' => $w, 'lvl' => $parts['lvl']);
		}
		
		uksort($acls, create_function('$a, $b', '
			$agrp = preg_match("`^@.+`", $a);
			$bgrp = preg_match("`^@.+`", $b);
			if($agrp && !$bgrp) return -1;
			if(!$agrp && $bgrp) return 1;
			return strcmp($a, $b);
		'));
		
		foreach($acls as $who => $list) {
			uasort($list, create_function('$a, $b', '
				if($a["what"] == $b["what"]) return $a["lvl"] - $b["lvl"];
				$a["what"] = str_replace("*", "", $a["what"]);
				$b["what"] = str_replace("*", "", $b["what"]);
				if(strpos($a["what"], $b["what"]) === 0) return 1;
				if(strpos($b["what"], $a["what"]) === 0) return -1;
				return 0;
			'));
			$lvl = max(array_map(create_function('$p', 'return $p["lvl"];'), $list));
			$slvl = -1;
			foreach($list as $l) if($l['what'] == $resource) $slvl = $l['lvl'];
			$list['_lvl'] = ($slvl >= 0) ? $slvl : $lvl;
			$acls[$who] = $list;
		}
		
		if($locale) foreach($acls as $who => $list) foreach($list as $rule => $d) if($d['what'] != $resource) unset($acls[$who][$rule]);
		
		return $acls;
	}
	
	/**
	 * Get acls of a user/group for a resource
	 * @param $of user or group to get acl list from
	 * @param $resource page id or media id to get acl list about
	 * @return array
	 */
	function getACLon($of, $resource) {
		$this->init();
		
		$flt = array($of);
		if(!preg_match('`^@.+`', $of)) {
			$flt[] = '@ALL';
			if(isset($this->usersgroups[$of])) $flt = array_merge($flt, $this->usersgroups[$of]);
		}
		
		$acls = array();
		
		$glvl = 0;
		$gspecificlvl = -1;
		
		foreach($this->IDPathTree($resource) as $id) {
			$acls[$id] = $this->resourceACLs($id, true);
			$lvl = 0;
			$specificlvl = -1;
			foreach($acls[$id] as $who => $d) {
				if(!in_array($who, $flt)) {
					unset($acls[$id][$who]);
					continue;
				}
				foreach($d as $r => $rd) {
					if($rd['rule'] == 'ADMIN') {
						$lvl = AUTH_ADMIN;
					}elseif($rd['lvl'] > $lvl) $lvl = $rd['lvl'];
					
					if(($rd['what'] == $id) && ($rd['lvl'] > $specificlvl))  $specificlvl = $rd['lvl'];
				}
			}
			
			$acls[$id]['_lvl'] = $lvl;
			$acls[$id]['_specificlvl'] = $specificlvl;
			
			if($lvl > $glvl) $glvl = $lvl;
			if($specificlvl >= 0)  $gspecificlvl = $specificlvl;
		}
		
		$acls['_lvl'] = $glvl;
		$acls['_specificlvl'] = $gspecificlvl;
		
		return $acls;
	}	
		
	/**
	 * Gets the acl list of an user/group
	 * @param $of string what to get acls of
	 * @return array
	 */
	function getACL($of) {
		$this->init();
		
		$acls = array();
		
		$of = array($of);
		if(!preg_match('`^@.+`', $of[0]) && isset($this->usersgroups[$of[0]])) {
			if(auth_isadmin($of[0], $this->usersgroups[$of[0]])) $acls['*'] = array('ADMIN' => array('what' => '*', 'who' => null, 'rule' => 'ADMIN', 'lvl' => AUTH_ADMIN));
			$of = array_merge($of, $this->usersgroups[$of[0]]);
		}
		foreach($this->acls as $w => $parts) {
			if(!in_array($parts['who'], $of)) continue;
			if(!isset($acls[$parts['what']])) $acls[$parts['what']] = array();
			$acls[$parts['what']][$w] = array('what' => $parts['what'], 'who' => $parts['who'], 'rule' => $w, 'lvl' => $parts['lvl']);
		}
		
		uksort($acls, create_function('$a, $b', '
			if($a == $b) return 0;
			$a = str_replace("*", "", $a);
			$b = str_replace("*", "", $b);
			if(($b == "") || strpos($a, $b) === 0) return 1;
			if(($a == "") || strpos($b, $a) === 0) return -1;
			return 0;
		'));
		foreach($acls as $what => $list) {
			uasort($list, create_function('$a, $b', 'return $a["lvl"] - $b["lvl"];'));
			$list['_lvl'] = max(array_map(create_function('$p', 'return $p["lvl"];'), $list));
			$acls[$what] = $list;
		}
		
		return $acls;
	}
	
	/**
	 * Parses the acl file into an array and loads users-groups hash
	 */
	function init() {
		if(is_null($this->acls)) {
			global $AUTH_ACL;
			foreach($AUTH_ACL as $l) {
				if(preg_match('`^\s*(#.*)$`', $l)) continue;
				list($what, $who, $lvl) = preg_split('`\s+`', $l);
				if(!$what || !$who || !$lvl) continue;
				$this->acls[$what.' '.$who.' '.$lvl] = array(
					'what' => $what,
					'who' => $who,
					'lvl' => (int)$lvl
				);
			}
		}
		
		if(is_null($this->usersgroups)) {
			global $auth;
			$this->usersgroups = array();
			foreach($auth ? $auth->retrieveUsers() : array() as $u => $info) {
				$this->usersgroups[$u] = array_map(create_function('$g', 'return "@".$g;'), $info['grps']);
				$this->usersgroups[$u][] = '@ALL';
			}
		}
	}
	
	/**
	 * Decomposes an ID into ACLs compliant namespace tree
	 * @param $id string id to decompose
	 * @return array
	 */
	function IDPathTree($id) {
		$id = cleanID($id);
		if(!@file_exists(wikiFN($id)) && !@file_exists(mediaFN($id))) $id .= ':*';
		$tree = $id ? array($id) : array();
		while($id = getNS($id)) $tree[] = $id.':*';
		$tree[] = '*';
		return array_reverse(array_unique($tree));
	}
}
