Skip to content
Snippets Groups Projects
API.php 59.24 KiB
<?php
setlocale(LC_ALL, 'en_GB.UTF8');
date_default_timezone_set('UTC');

require(eduGAIN_config."access_keys.php");
if (isset($_SESSION) && isset($_SESSION['auth_ok']) && isset($_SESSION['user']) && $_SESSION['auth_ok'] == 'authenticated') {
    $user = $_SESSION['user'];
}

class API {

    public function __construct() {
        $this->action = isset($_REQUEST['action']) ? $_REQUEST['action'] : 0;
        $this->opts = [];
        $this->fed_id = 0;
        if (!empty($_REQUEST['edugain'])) {
            $this->fed_id = $_REQUEST['edugain'];
        }
        if (!empty($_REQUEST['fed_id'])) {
            $this->fed_id = $_REQUEST['fed_id'];
        }
        // first validate that the input code maches the allowed patterns 
        if (!preg_match('/^:?([a-zA-Z]*-?[a-zA-Z]*)(:[a-aA-Z]*-?[a-zA-Z]*)*$/', $this->fed_id)) {
            $this->fed_id = 0;
        }
        
        // check if reg_auth has has been provided (only if fed_id is missing
        if ($this->fed_id === 0 && !empty($_REQUEST['reg_auth']) ) {
            $edugain = new eduGAIN_core();
            $this->fed_id = $edugain->getCodeFromRegAuth($_REQUEST['reg_auth']);
        }

        // we have moved from country codes to federations but we are still supporting the old model
        if ($this->fed_id !== 0) {
            $edugain = new eduGAIN_core();
            $this->fed_id = $edugain->getCode($this->fed_id);
        }

        // default reply format is 'json'
        $this->format = 'json';
        if (isset($_REQUEST['format'])) {
            if ($_REQUEST['format'] == 'print_r') {
                $this->format = 'print_r';
            }
            if ($_REQUEST['format'] == 'xml') {
                $this->format = 'xml';
            }
            if ($_REQUEST['format'] == 'csv') {
                $this->format = 'csv';
            }
            if ($_REQUEST['format'] == 'html') {
                $this->format = 'html';
            }
            if ($_REQUEST['format'] == 'png') {
                $this->format = 'png';
            }
            if ($_REQUEST['format'] == 'internal') {
                $this->format = 'internal';
            }
        }

        // now check that request parameters are supported
        if (isset($_REQUEST['key']))
            $this->key = $_REQUEST['key'];
        else
            $this->key = 0;

        $supportedOptions = [
            'opt',
            'type',
            'entity_cat',
            'eccs_status',
            'ec', // entity category
            'ecs', // entity category support
            'row_id',
            'entityid', // this is an alias for e_id it is important that it comes after e_id
            'e_id',
            'proto', // is serves no putpose now, but lef to avoid old links from failing
            'clashes',
            'warnings',
            'coco',
            'sirtfi',
            'search_type',
            'help',
            'date',
            'start_date',
            'end_date',
            'not_empty',
            'map_size',
            'map_type',
            'show_list',
            'new_entities',
            'only_errors',
            'url_type',
            'mod_time',
            'lang',
            'details',
        ];

        foreach ($supportedOptions as $opt) {
            $this->assignOpt($opt);
        }
//        print_r($this->opts);
    }

    private function optionError($optName) {
        header('Content-Type: text/html; charset=utf-8');
        print ("incorrect value for option $optName");
        die("incorrect value for option $optName");
    }

    /* check that input provided by the user is valid and secure, many checkes are repeated to make library call secure also when called by a different path */

    private function assignOpt($optName) {
        $optValue = isset($_REQUEST[$optName]) ? $_REQUEST[$optName] : 'NOTSET';
        switch ($optName) {
            case 'opt':
                break;
            case 'type':
                if ($optValue === 0 || $optValue === 'NOTSET' || $optValue === 'all') {
                    $optValue = 7;
                }
                switch ($optValue) {  // these values can be ALL, IDP, SP, AA
                    case 'NOTSET':
                        $optValue = 7;
                    case 'idp' :
                        $optValue = 4;
                        break;
                    case 'sp' :
                        $optValue = 2;
                        break;
                    case 'aa' :
                        $optValue = 1;
                        break;
                    case 7;
                    case 4;
                    case 2;
                    case 1;
                        break;
                    default:
                        $this->optionError($optName);
                        break;
                }
                break;
            case 'entity_cat': // security checks are complex and have been moved to eduGAIN_entity::getEntityList
                if ($optValue === 0 || $optValue === 'NOTSET') {
                    $optValue = -2;
                }
                break;
            case 'eccs_status':
                switch ($optValue) {   // these values can be only 0,1,2,3,4
                    case 'NOTSET':
                        $optValue = 0;
                        break;
                    case 0:
                        break;
                    default:
                        if (isset(Constants::ECCS_Colours[$optValue])) {
                            break;
                        }
                        $this->optionError($optName);
                        break;
                }
                break;
            case 'ec':
                switch ($optValue) {   // these values can be only 0,1
                    case 'NOTSET':
                        $optValue = 0;
                    case 0;
                    case 1;
                        break;
                    default:
                        $this->optionError($optName);
                        break;
                }
                break;
            case 'ecs':
                switch ($optValue) {   // these values can be only 0,1
                    case 'NOTSET':
                        $optValue = 0;
                    case 0;
                    case 1;
                        break;
                    default:
                        $this->optionError($optName);
                        break;
                }
                break;
            case 'entityid': // alias for e_id;
                $optName = 'e_id';
                break;
            case 'e_id':  // beware this value is an URI therefore require proper cleaning, this is done in eduGAIN_entity::getEntityList
                if ($optValue == 'NOTSET') {
                    $optValue = $this->opts['e_id'];
                }
                break;
            case 'row_id': // database row identifier from the entities table
                if ($optValue == 'NOTSET') {
                    $optValue = 0;
                }
                if (!is_numeric($optValue)) {
                    $this->optionError($optName);
                }
                break;
//            case 'proto':
            case 'clashes':
            case 'warnings':
            case 'coco':
            case 'sirtfi':
                if ($optValue == 'NOTSET') {
                    $optValue = 0;
                }
                break;
            case 'search_type':
                switch ($optValue) {
                    case 'entityid':
                    case 'text':
                    case 'rev-text':
                        break;
                    default:
                        $optValue = '';
                }
                break;
            case 'help':
                if ($optValue == 'NOTSET') {
                    $optValue = 0;
                } else {
                    $optValue = 1;
                }
                break;
            case 'show_list':
            case 'not_empty':
                if ($optValue == 'NOTSET') {
                    $optValue = 0;
                } else {
                    if ($optValue !== '0') {
                        $optValue = 1;
                    }
                }
                break;
            case 'date':
            case 'start_date':
            case 'end_date':
                if (!preg_match('/^\d\d\d\d-\d\d-\d\d$/', $optValue)) {
                    $optValue = 0;
                } else {
                    $optValue = strtotime($optValue);
                }
                break;
            case 'mod_time':
                if ($optValue == 'NOTSET') {
                    $optValue = 24;
                } elseif (!preg_match('/^\d\d?$/', $optValue) && !preg_match('/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d$/', $optValue)) {
                    $this->optionError($optName);
                }
                break;
            case 'map_size':
                if ($optValue == 'NOTSET' || $optValue != 'small') {
                    $optValue = 'l';
                } else {
                    $optValue = 's';
                }
                break;
            case 'map_type':
                if ($optValue == 'NOTSET' || $optValue != 'europe') {
                    $optValue = 'world';
                }
                break;
            case 'new_entities':
                if ($optValue === 0 || $optValue === 'NOTSET') {
                    $optValue = 7;
                }
                switch ($optValue) {  // these values can be 
                    case 'NOTSET':
                        $optValue = 7;
                    case 7:
                    case 5:
                    case 4:
                    case 2:
                    case 1:
                        break;
                    default:
                        $this->optionError($optName);
                        break;
                }
                break;
                    case 'url_type':
                        // there will be additional sanitasation in functions that use that
                        $optValue = filter_var($optValue, FILTER_SANITIZE_URL);
                        break;
            case 'lang':
                if (preg_match('/^([a-z][a-z])/', $optValue, $m)) {
                    $optValue = $m[1];
                } else {
                    $optValue = 'NOTSET';
                }
                break;
            case 'only_errors':
                if ($optValue != 1) {
                    $optValue = 0;
                }
            case 'details':
                if ($optValue != 1) {
                    $optValue = 0;
                }

            default:
        }
        $this->opts[$optName] = $optValue;
    }

    private function description($desc) {
        $this->descriptionArray = $desc;
    }

    private function printHelpHeaders() {
        print '<body><div class="api-help">
<h1>Documentation of eduGAIN database access API</h1>';
    }

    private function printDescription() {
        $desc = $this->descriptionArray;
        header('Content-type: text/html; charset=utf-8');
        $this->printHelpHeaders();
        print "<h2>Method: $this->action</h2>";
        print "<h3>Description:</h3>";
        print $desc['header'];
        print "<p class='api-help-formats'><strong>Supported formats:</strong> ";
        print implode(', ', $desc['supportedFormats']);
        print "<p><strong>Arguments used:</strong><dl>";
        foreach ($desc['arguments'] as $arg) {
            $req = $arg['required'] ? 'required' : 'optional';
            print "<dt><b>".$arg['arg']."</b> ($req)</dt><dd>";

            if (!$arg['required'] && isset($arg['default'])) {
                print "default: ".$arg['default']."<br />";
            }
            print "allowed values: ";
            if (is_array($arg['values'])) {
                
            } else {
                print $arg['values'];
            }
            if (!empty($arg['description'])) {
                print "<br><i>".$arg['description']."</i>";
            }
            print "</dt>";
        }
        print "</dl>";
        if (!empty($desc['returns']['description'])) {
            print "<p><strong>Returns:</strong><br>";
            print $desc['returns']['description'];
        }
        if (!empty($desc['examples'])) {
            print "<p><strong>";
            $exURLs = '';
            if(count($desc['examples']) > 1) {
                print "Examples";
            } else {
                print "Example";
            }
            print ":</strong><br><dl>";
            foreach ($desc['examples'] as $example) {
                $format = '&format='.'print_r';
                if (isset($example['format'])) {
                    if ($example['format'] == 'default') {
                        $format = '';
                    } else {
                        $format = '&format='.$example['format'];
                    }
                }
                $exURL = ROOT_URL.'/api.php?action='.$this->action.$format;
                foreach ($example['opt'] as $exOption => $exOptionValue) {
                    $exURL .= '&'.$exOption.'='.$exOptionValue;
                }
                print '<dt>'.$example['title'].'</dt>';
                print '<dd><a href="'.str_replace("&","&amp;",$exURL).'">'.str_replace("&","&amp;",$exURL).'</a></dd>';
            }
            print $exURLs;
        }
        print '</dl><p><a href="api.php">Show full action list</a>';
        print '</div></body></html>';
    }

    private $action;
    private $format;
    private $opts;
    private $fed_id;
    private $key;
    private $descriptionArray = [];

// This is the generic wrappper around all actions

    public function run_action() {
        $a_name = 'action_'.$this->action;
        if (!method_exists($this, $a_name)) {
            $a_name = 'list_api_actions';
        }
        $this->output($this->$a_name());
    }

// API actions start here. Each AIP should set the description property explaining what it does

    private function action_new_fed_code() {
        $this->description([
            'header' => "Get the federation new code from the old one. If no mapping is available, the input code will be returned.",
            'supportedFormats' => ['json', 'print_r'],
            'arguments' => [
                $this->addStdArgument('fed_id'),
                $this->addStdArgument('format'),
            ],
            'returns' => ['description' => 'The current code assigned to the federation.',]
        ]);

        if ($this->opts['help'] == 1) {
            return "";
        }
        return($this->fed_id);
    }
    private function action_list_fed_codes() {
        $this->description([
            'header' => "List federations codes and their names.",
            'supportedFormats' => ['json', 'print_r'],
            'arguments' => [
                [
                    'arg' => 'opt',
                    'required' => FALSE,
                    'default' => '0',
                    'values' => '0, 1, 2, 3, 4',
                    'description' => '0 - show all; 1 - only production feds; 2 - all members; 3 - only candidates; 4 - only voting-only',
                ],
                $this->addStdArgument('format'),
            ],
            'returns' => [
                'description' => 'An array of code,name pairs.',
            ],
            'examples' => [
                ['opt' => [], 'title' => 'all federations'],
                ['opt' => ['opt' => 3], 'title' => 'show candidates only'],
            ]
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }
        $productionOnly = $this->opts['opt'];
        if ($productionOnly != 0 && $productionOnly != 1 && $productionOnly != 2 && $productionOnly != 3 &&$productionOnly != 4) {
            $productionOnly = 0;
        }
        $edugain = new eduGAIN($productionOnly);
        return $edugain->listFedCodes();
    }

    private function action_list_feds() {
        $this->description([
            'header' => "List federation details (code, name, contact email, registration authority string, status).",
            'supportedFormats' => ['json', 'print_r'],
            'arguments' => [
                [
                    'arg' => 'opt',
                    'required' => FALSE,
                    'default' => '0',
                    'values' => '0, 1, 2, 3, 4',
                    'description' => '0 - show all; 1 - only production feds; 2 - all members; 3 - only candidates; 4 - only voting-only'],
                $this->addStdArgument('format'),
            ],
            'returns' => [
                'description' => 'a fed_id indexed array of basic federatins info:<ul>
                <li>name - federation name
                <li>email - contact email
                <li>reg_auth - registrationAuthority value
                <li>status - numeric representation of status: 1 - candidate, 4 - voting-only, 6 - participant
              </ul>'
            ],
            'examples' => [
                ['opt' => [], 'title' => 'show all feds'],
                ['opt' => ['opt' => 1], 'title' => 'show all production feds'],
                ],
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }
        $out = [];
        if ($this->opts['opt'] != 1 && $this->opts['opt'] != 2 && $this->opts['opt'] != 3 && $this->opts['opt'] != 4) {
                $this->opts['opt'] = 0;
        }
        $edugain = new eduGAIN($this->opts['opt']);
        $F = $edugain->FEDS;
        ksort($F, SORT_STRING | SORT_FLAG_CASE);
        foreach ($F as $fed => $A) {
            $out[$fed] = ['name' => $A['name'],
                'email' => $A['contact_email'],
                'reg_auth' => $A['reg_auth'],
                'status' => $A['status'],
                'membership_date' => $A['membership_date'],
                'production_date' => $A['production_date']
                ];
        }
        return $out;
    }

    private function action_show_federation() {
        global $accessKeys;
        $this->description([
            'header' => "Show federation detils.",
            'supportedFormats' => ['json', 'print_r'],
            'arguments' => [
                $this->addStdArgument('fed_id', TRUE),
                $this->addStdArgument('reg_auth'),
                $this->addStdArgument('format'),
            ],
            'returns' => ['description' => 'returns all details about the federation, authenticated users will also see e-mail addresses of federation delegates and deputis.'],
            'examples' => [
                ['opt' => ['fed_id' => 'PIONIER-ID'], 'title' => 'PIONIER.Id federation details'],
                ['opt' => ['reg_auth' => 'https://aai.pionier.net.pl'], 'title' => 'PIONIER.Id federation details, but called with the reg_auth'],
                ],
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }
        if ($this->fed_id === 0) {
            return "";
        }
        $edugain = new eduGAIN(-1, $this->fed_id);
        $show_email = 0;
        if (isset($_SESSION) && isset($_SESSION['auth_ok']) && isset($_SESSION['user']) && $_SESSION['auth_ok'] == 'authenticated') {
            $user = $_SESSION['user'];
            $show_email = 1;
        } elseif (!empty($this->key) && in_array($this->key, $accessKeys)) {
            $show_email = 1;
        }
        $edugain->load_federation_details('sha1', $show_email);
        $out = $edugain->FEDS[$this->fed_id];
        return $out;
    }

    private function action_list_eccs_idps() {
        $this->description([
            'header' => "This method is exclusively for the ECCS service.",
            'supportedFormats' => ['json', 'print_r'],
            'arguments' => [
                $this->addStdArgument('format'),
            ]
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }
        $e = new eduGAIN_entity();
        $out = $e->getECCSEntityList();
        return $out;
    }

    private function action_edugain_status() {
        $this->description([
            'header' => "Provides basic status information about metadata aggregation.",
            'supportedFormats' => ['json', 'print_r'],
            'arguments' => [
                $this->addStdArgument('fed_id'),
                $this->addStdArgument('reg_auth'),
                $this->addStdArgument('format'),
            ],
            'returns' => [
                'description' => 'if a valid federation code is present return an array of:<ul><li>code -  the Federation Code
                    <li>feed_pull_time - the UTC timestamp of last successful mmetadata pull
                    <li>last_validation - last successfull metadata validation
                    <li>valid_until - the validUntil value as set in last successfully validated federation metadata
                    <li>creationinstant - the creationInstant value as set in last successfully validated federation metadata
                    <li>valid_sec - validity time remaining in seconds
                    <li>feed_problem - 0 when all is fine, 1 when validation fails, 2 when feed is unavailable
                    <li>feed_problem_desc - a textual description of the problem
                    </ul>
                   when no federation code is present an array of code-indexed participating federations with details as described above'
            ],
            'examples' => [
                ['opt' => ['fed_id' => 'PIONIER-ID',],'title' => 'show status of PIONIER.Id',],
                ['opt' => ['fed_id' => 'PIONIER-ID',], 'format' => 'json', 'title' => 'show status of PIONIER.Id in JSON',]
            ]
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }
        $edugain = new eduGAIN(1, $this->fed_id, true);
        $edugain->load_federations_state();
        $out = $edugain->FEDS;
        return $out;
    }

    private function action_list_entities() {
        $this->description([
            'header' => "Provides an entities listing according to search parameters provided.",
            'supportedFormats' => ['json', 'print_r', 'csv', 'internal'],
            'arguments' => [
                [
                    'arg' => 'type',
                    'required' => FALSE,
                    'values' => '7  or "all" - all entities; 4 or "idp"  - IdPs only; 2 or "sp" - SPs only; 1 or "aa" - standalone AAs only.',
                    'default' => 7,
                    'description' => 'limits the results to a givien entity type',
                ],
                $this->addStdArgument('fed_id'),
                [
                    'arg' => 'entity_cat',
                    'required' => FALSE,
                    'values' => '-2 - no filter; -1 - any category; any valid entitycategory identiefier (URL encoded)',
                    'default' => -2,
                    'description' => 'limits the results to a given entity category identifier',
                ],
                [
                    'arg' => 'ec',
                    'required' => FALSE,
                    'values' => '0 - filter not set; 1 - filter set',
                    'description' => 'only display entities which announce the given entity category',
                ],
                [
                    'arg' => 'ecs',
                    'required' => FALSE,
                    'values' => '0 - filter not set; 1 - filter set',
                    'description' => 'only display entities which announce support for the given entity category',
                ],
                [
                    'arg' => 'e_id',
                    'required' => FALSE,
                    'values' => 'any string',
                    'description' => 'input for the entity search',
                ],
                [
                    'arg' => 'search_type',
                    'required' => FALSE,
                    'values' => 'entityid - EntityID substring match; text - Whole entity multiword search; rev-text - Reverse whole entity multiword search (this will throw away rows which match the provided words',
                    'description' => 'determine the type of search to be performed; while this argument is optional, in case when e_id is set search_type has to be set as well.',
                ],
                [
                    'arg' => 'eccs_status',
                    'required' => FALSE,
                    'default' => '0',
                    'values' => '0 - no filter; 1 - status OK; 2 - status Warning; 3 - status Error',
                    'description' => 'filter on eduGAIN Connectivity Check Service',
                ],
                [
                    'arg' => 'clashes',
                    'required' => FALSE,
                    'default' => '0',
                    'values' => '0 - no info shown; 1 - show clashes info; 2 - show only entities appearing in more then one federation',
                    'description' => 'display/filter on entity clashes (entities appearing in more then one federation)',
                ],
                [
                    'arg' => 'warnings',
                    'required' => FALSE,
                    'default' => '0',
                    'values' => '0 - no filter; 2 - show only entities with validator warnings, 3 - show only entities with warnings except for those which only report missing logos',
                    'description' => 'filter on validator warnings',
                ],
                [
                    'arg' => 'coco',
                    'required' => FALSE,
                    'default' => '0',
                    'values' => '0 - no filter; 2 - any status; 3 - warnings and errors; 4 - only errors',
                    'description' => 'filter on Code of Conduct monitoring status',
                ],
                [
                    'arg' => 'sirtfi',
                    'required' => FALSE,
                    'default' => '0',
                    'values' => '0 - no filter; 1 - SIFTI asserted via the dedicated attibute value; 2 - formal SIRTFI errors',
                    'description' => 'filter on REFEDS SITFI status',
                ],
                $this->addStdArgument('format'),
            ],
            'examples' => [
                ['opt' => [], 'title' => 'All entities'],
                ['opt' => [], 'title' => 'All entities in CSV', 'format' => 'csv'],
                ['opt' => [ 'clashes' => 2], 'title' => 'clashes - eneties in several federations'],
                ['opt' => [ 'eccs_status' => 3], 'title' => 'entities with ECCS errors'],
            ],
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }
        $out = [];
        $e = new eduGAIN_entity();
        $startDate = $this->opts['start_date'];
        $endDate = $this->opts['end_date'];
        switch ($this->opts['new_entities']) {
            case 7:
                $startDate = strtotime("2017-04-01");
                break;
            case 5:
                $startDate = strtotime(date("Y", strtotime("now"))."-01-01");
                break;
            case 4:
                $startDate = strtotime(date("Y-m", strtotime("now"))."-01");
                break;
            case 2:
                $startDate = strtotime("-1 week");
                break;
            case 1:
                break;
        }
        if ($this->format == 'print_r' || $this->format == 'json' || $this->format == 'csv') {
            $out = $e->getEntityList($this->opts['type'],
                $this->fed_id,
                $this->opts['entity_cat'],
                $this->opts['ec'], 
                $this->opts['ecs'], 
                $this->opts['e_id'],
                $this->opts['search_type'],
                $this->opts['eccs_status'],
                $this->opts['clashes'],
                $this->opts['warnings'], 
                $this->opts['coco'], 
                $this->opts['sirtfi'], 
                $startDate,
                $endDate, 
                $this->format);
            return($out);
        }
        $this->format = 'json';
        $out['html'] = $e->getEntityList($this->opts['type'], 
                $this->fed_id, 
                $this->opts['entity_cat'], 
                $this->opts['ec'], 
                $this->opts['ecs'], 
                $this->opts['e_id'], 
                $this->opts['search_type'], 
                $this->opts['eccs_status'], 
                $this->opts['clashes'], 
                $this->opts['warnings'], 
                $this->opts['coco'], 
                $this->opts['sirtfi'], 
                $startDate,
                $endDate, 
                'internal');
        $out['stats'] = $e->globalStats;
        return $out;
    }

    private function action_list_feds_full() {
        global $accessKeys;
        $this->description([
            'header' => "List full federation details incliding contact e-mails to federation representatives.",
            'supportedFormats' => ['xml - this is set automatically and need not be specified'],
            'arguments' => [
                [
                    'arg' => 'key',
                    'required' => TRUE,
                    'values' => 'secret value provided by eduGAIN OT',
                    'description' => 'This method is resticted since it shows federation representatives e-mails even in cases where they have been declared not to be public.',
                ],
                $this->addStdArgument('format'),
            ]
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }
        if (!in_array($this->key, $accessKeys))
            return;
        $edugain = new eduGAIN();
        $edugain->load_all();
        $out = $edugain->FEDS;
        return $out;
    }
    
    private function action_modified_entities() {
        $this->description([
            'header' => "Show the list of entities modified recently",
            'supportedFormats' => ['json', 'print_r'],
            'arguments' => [
                [
                    'arg' => 'mod_time',
                    'required' => FALSE,
                    'values' => 'an integer between 1 and 23 or a valid UTC time in yyyy-mm-ddTHH:MM:SS format',
                    'description' => 'the starting time limit for the list to show - missing argment is interpreted ans last 24 hours, '
                    . 'integer between 1 and 23 is the number of hours back',
                ],

                $this->addStdArgument('format'),
            ],
            'examples' => [
                ['opt' => [], 'title' => 'show entities modified in last 24 hours'],
                ['opt' => ['mod_time' => '2023-01-01T00:00:00'], 'title' => 'show entities modified since 2023-01-01'],
            ],
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }
        
        $modTime = $this->opts['mod_time'];
        if (preg_match('/^\d\d?$/', $modTime) && $modTime > 0 && $modTime < 25) {
            $searchTime = strtotime("-$modTime hours");
        } elseif (preg_match('/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d$/', $modTime)) {
            $searchTime = strtotime($modTime);
        } else {
            $this->optionError($optName);
        }
        $e = new eduGAIN_entity();
        $out = $e->getModified($searchTime);
        return $out;
    }

    private function action_show_entity_clashes() {
        $this->description([
            'header' => "Show federations supplyilng this entity. Will only work for entities appearing in more then one federation. This method is meant for internal use.",
            'supportedFormats' => ['xml - this is set automatically and need not be specified'],
            'arguments' => [
                [
                    'arg' => 'row_id',
                    'required' => TRUE,
                    'values' => 'valid internal identifier',
                    'description' => 'internal database identity identifier',
                ],
                $this->addStdArgument('format'),
            ]
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }
        $e = new eduGAIN_entity();
        $out = $e->showEntityClashes($this->opts['row_id']);
        return $out;
    }

    private function action_show_entity() {
        $this->description([
            'header' => "Show the full entityID XML. This method is mostly for internal use.",
            'supportedFormats' => ['xml - this is set automatically and need not be specified'],
            'arguments' => [
                [
                    'arg' => 'row_id',
                    'required' => FALSE,
                    'values' => 'valid internal identifier',
                    'description' => 'internal database identity identifier, either e_id or row_id is required',
                ],
                [
                    'arg' => 'e_id',
                    'required' => FALSE, 
                    'values' => 'a valid entityId',
                    'description' => 'a valid entityId (full string), either e_id or row_id is required, e_id takes precedence',
                ],
            ],
            'examples' => [
                ['opt' => ['e_id' => 'https://sso.umk.pl/idp/shibboleth'],
                    'format' => 'default',
                    'title' => 'download entity metadata vased on the editityId',
                ],
            ]
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }
        $this->format = 'xml';
        $e = new eduGAIN_entity();
        $out = $e->showEntity($this->opts['row_id'], $this->opts['e_id']);
        return $out;
    }
    
    private function action_show_entity_details() {
                $this->description([
            'header' => "Show the full entity info. This method is mostly for internal use.",
            'supportedFormats' => ['json', 'html', 'print_r'],
            'arguments' => [
                [
                    'arg' => 'row_id',
                    'required' => FALSE,
                    'values' => 'valid internal identifier',
                    'description' => 'internal database identity identifier, either e_id or row_id is required, e_id takes precedence',
                ],
                [
                    'arg' => 'e_id',
                    'required' => FALSE, 
                    'values' => 'a valid entityId',
                    'description' => 'a valid entityId (full string), either e_id or row_id is required',
                ],
                [
                    'arg' => 'opt',
                    'required' => FALSE,
                    'values' => '"internal" or none',
                    'description' => 'only useful with the html format setting, internal is meant for internal'
                    . ' system use and does not contain the CSS info.'
                ],
                $this->addStdArgument('format'),
            ],
            'examples' => [
                ['opt' => ['e_id' => 'https://sso.umk.pl/idp/shibboleth', 'opt' => 'standalone'],
                    'format' => 'html',
                    'title' => 'show the entity on a standalone HTML page'],
                ['opt' => ['e_id' => 'https://sso.umk.pl/idp/shibboleth'],
                    'format' => 'default',
                    'title' => 'show the entity info in json format'],
            ],
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }
        $e = new eduGAIN_entityDetails();
        $entity_html = $e->printEntity($this->opts['row_id'], $this->opts['e_id']);
        if ($this->format !== 'html') {
            return $e->entity;
        }
        if ($this->opts['opt'] !== 'internal' && $this->format === 'html') {
            $out = $e->printHeader();
        }
        $out .= $entity_html;
        return $out;
        
    }

    private function list_api_actions() {
        $this->description(
            [
                'header' => "Show the list of available actions",
                'supportedFormats' => ['format setting is not used'],
            ]
        );
        if ($this->opts['help'] == 1) {
            return "";
        }
        $this->format = 'html';
        $A = get_class_methods('API');
        $Out = [];
        foreach ($A as $a) {
            if (preg_match('/^action_/', $a))
                $Out[] = substr($a, 7);
        }
        sort($Out);
        if ($this->format == 'html') {
            $this->printHelpHeaders();
            $out = '<p>
The general API call syntax is by the specification of the <i>action</i> argument:
<pre>'.ROOT_URL.'/api.php?action=action_name</pre>
If either no action or an unrecognised action is specified <b>this</b> page is shown.
Additional arguments can be added and they are specified in every action escription. Adding the <i>help</i> argument will cause the description to be displayed and all other arguments be disregarded.
The <i>format</i> argument is common to all API calls and in most cases allows to generate the result in a desired form. Currently each action needs to be consulted for a list of supported formats.
<p>
Click action names for a full description.</p>';
            $out .= '<ul>';
            foreach ($Out as $action) {
                $out .= '<li><a href="api.php?action='.$action.'&help">'.$action.'</a></li>';
            }
            $out .= '</ul>';
        } else {
            $out = $Out;
        }
        $out .= "</div>";
        return $out;
    }

    private function action_get_entity_name() {
        $this->description([
            'header' => "Show the entity display name, this works as a name resolver of sorts. The name is shown in the specified lanuage (if the corresponfing value exists) or in English as a fallback.",
            'supportedFormats' => ['print_r', 'json'],
            'arguments' => [
                [
                    'arg' => 'e_id',
                    'required' => TRUE,
                    'values' => 'eitityID string',
                    'description' => 'entityID to search for',
                ],
                [
                    'arg' => 'type',
                    'required' => FALSE,
                    'values' => '7  or "all" - all entities; 4 or "idp"  - IdPs only; 2 or "sp" - SPs only; 1 or "aa" - standalone AAs only.',
                    'default' => 7,
                    'description' => 'limits the results to a givien entity type',
                ],
                [
                    'arg' => 'lang',
                    'required' => FALSE,
                    'values' => '',
                    'description' => ''
                ],
                [
                    'arg' => 'opt',
                    'required' => FALSE,
                    'values' => '1 show request innitiator; none or any other - do not show',
                    'description' => ''
                ]
            ],
            'returns' => ['description' => 'an array of names assigned to entity roles'],
            'examples' => [
                    [ 'opt' => ['e_id' => 'https://sso.umk.pl/idp/shibboleth'], 'title' => 'show default name'],
                    ['opt' => ['e_id' => 'https://sso.umk.pl/idp/shibboleth',
                    'lang' => 'pl',],
                    'title' => 'show the Polish name'],
            ],
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }
        if ($this->opts['e_id'] == 'NOTSET') {
            return "";
        }
        $e = new eduGAIN_entity();
        $details = $e->showEntityName($this->opts['e_id'], $this->opts['lang'], $this->opts['type']);
        if ($this->opts['opt'] == 1) {
            $out = $details;
        } else {
            $out = $details[0];
        }
        return $out;
    }

    private function action_show_global_history() {
        $this->description([
            'header' => "Show global eduGAIN history.  In no <i>data</i>, <i>start_date</i>, <i>end_date</i> arguments are specified, the current day is used",
            'supportedFormats' => ['json', 'print_r'],
            'arguments' => [
                $this->addStdArgument('date'),
                $this->addStdArgument('start_date'),
                $this->addStdArgument('end_date'),
                [
                    'arg' => 'details',
                    'required' => FALSE,
                    'default' => '0',
                    'values' => '1 - show history details, any other value is mapped to 0',
                    'description' => '',
                ],
                [
                    'arg' => 'not_empty',
                    'required' => FALSE,
                    'default' => '0',
                    'values' => '0 - no filter, 1 - apply filter',
                    'description' => 'Show only those history entires that have enitiy count filled in (older statistics are not complete)',
                ],
                $this->addStdArgument('format'),
            ],
            'returns' => ['description' => ''],
            'examples' => [
                ['opt' =>[], 'title' => 'show current numbers'],
                ['opt' =>['start_date' => '2023-01-01'], 'title' => 'show history since 2023-01-01'],
                ['opt' =>['start_date' => '2023-01-01', 'details' => 1], 'title' => 'show history since 2023-01-01 with more details'],
            ],
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }
        $startDate = $this->opts['start_date'];
        $endDate = $this->opts['end_date'];
        if ($this->opts['date']) {
            $startDate = $this->opts['date'];
            $endDate = $this->opts['date'];
        }
        $notEmpty = $this->opts['not_empty'];
        $edugainHistory = new eduGAIN_history();
        if ($this->opts['details'] == 1) {
            $out = $edugainHistory->getFullHistory($startDate, $endDate);
        } else {
            $out = $edugainHistory->getGlobalHistory($startDate, $endDate, $notEmpty);
        }
        return $out;
    }

    private function action_show_per_federation_history() {
        $this->description([
            'header' => "Show eduGAIN history in a per-federation split.  In no <i>data</i>, <i>start_date</i>, <i>end_date</i> arguments are specified, the current day is used",
            'supportedFormats' => ['json', 'print_r'],
            'arguments' => [
                $this->addStdArgument('fed_id'),
                $this->addStdArgument('reg_auth'),
                [
                    'arg' => 'date',
                    'required' => FALSE,
                    'default' => 'yesterday',
                    'values' => 'Any date in the format YYYY-MM-DD is allowed.',
                    'description' => 'The data for the single date is shown. If this argument is present then <i>start_date</i> and <i>end_date</i> are not taken into account.',
                ],
                $this->addStdArgument('start_date'),
                $this->addStdArgument('end_date'),
                $this->addStdArgument('format'),
            ],
            'returns' => ['description' => ''],
            'examples' => [
                ['opt' =>[], 'title' => 'show newest numbers'],
                ['opt' =>['start_date' => '2023-01-01'], 'title' => 'show history since 2023-01-01'],
                ['opt' =>['start_date' => '2023-01-01', 'details' => 1], 'title' => 'show history since 2023-01-01 with more details'],
            ],
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }

        $startDate = $this->opts['start_date'];
        if ($startDate == 0) {
            $startDate = -1;
        }
        $endDate = $this->opts['end_date'];
        if ($this->opts['date']) {
            $startDate = $this->opts['date'];
            $endDate = $this->opts['date'];
        }
        $edugainHistory = new eduGAIN_history();
        $out = $edugainHistory->getPerFederationHistory($this->fed_id, $startDate, $endDate);
        return $out;
    }

    private function action_list_federation_map_codes() {
        $this->description([
            'header' => "Show show country/region codes served by federations, used internally for map generation.",
            'supportedFormats' => ['json', 'print_r'],
            'arguments' => [
                $this->addStdArgument('format'),
                [
                    'arg' => 'opt',
                    'required' => FALSE,
                    'default' => '0',
                    'values' => '0, 1',
                    'description' => '0 - show all; 1 - only production feds',
                ],
            ],
        ]);
                
        if ($this->opts['help'] == 1) {
            return "";
        }
        $productionOnly = $this->opts['opt'];
        if ($productionOnly != 1) {
            $productionOnly = 0;
        }
        $edugain = new eduGAIN($productionOnly);
        $edugain->load_federation_map_codes();
        $F = $edugain->FEDS;
        ksort($F, SORT_STRING | SORT_FLAG_CASE);
        foreach ($F as $fed => $A) {
            $out[$fed] = ['code' => $fed, 'status' => $A['status'], 'country_code' => $A['country_code']];
        }
        return $out;
    }

    private function action_show_map() {
        $this->description([
            'header' => "Show eduGAIN maps as a PNG image",
            'supportedFormats' => ['png'],
            'arguments' => [
                [
                    'arg' => 'opt',
                    'required' => FALSE,
                    'default' => '0',
                    'values' => '0, 1',
                    'description' => 'with value 1 only show production federations',
                ],
                [
                    'arg' => 'map_type',
                    'required' => FALSE,
                    'values' => "europe, world",
                    'default' => 'world',
                    'description' => "Defines if the Europe or a full world map is to be shown",
                ],
                [
                    'arg' => 'map_size',
                    'required' => FALSE,
                    'default' => 'large',
                    'values' => 'large, small',
                    'description' => 'The map size, only matters for the world map',
                ],
            ],
            'returns' => ['description' => ''],
            'examples' => [
                ['opt' => [], 'title' => 'the default - large worl map'],
                ['opt' => ['map_type' => 'europe'], 'title' => 'the Europe map'],
                ['opt' => ['map_size' => 'small'], 'title' => 'small world map'],
            ],
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }
        $this->format = 'png';
        if ($this->opts['opt'] == 1) {
            $mapSuffix = '_active';
        } else {
            $mapSuffix = '';
        }

        if ($this->opts['map_type'] == 'europe') {
            $mapName = TECHNICAL_HOME.'/web/maps/europe'.$mapSuffix.'.png';
        } else {
            $mapName = TECHNICAL_HOME.'/web/maps/world_'.$this->opts['map_size'].$mapSuffix.'.png';
        }
        return file_get_contents($mapName);
    }

    private function action_show_entity_history() {
        $this->description([
            'header' => "Show a count or list of entities that have appeared in eduGAIN in a given period of time. 
               Entities that have appeared and also vanished in this perion and have been never seen again, are not counted
               unless specifically asked for. The list/count can be limited to a given role of and entity (IdP, SP, AA-only).",
            'supportedFormats' => ['json', 'print_r'],
            'arguments' => [
                [
                    'arg' => 'type',
                    'required' => FALSE,
                    'values' => '7 - all entities; 4 - IdPs only; 2 - SPs only; 1 - standalone AAs only.',
                    'default' => 7,
                    'description' => 'limits the results to a givien entity type',
                ],
                [
                    'arg' => 'opt',
                    'required' => FALSE,
                    'default' => '0',
                    'values' => '0, 1, 2',
                    'description' => 'with value 0 only show entities present in the current feed, with value 1 also show entities which have been withdrawn, with value 2 show withdrown entites only.',
                ],
                $this->addStdArgument('start_date'),
                $this->addStdArgument('end_date'),
                [
                    'arg' => 'show_list',
                    'required' => FALSE,
                    'values' => 'Not set or 0 - show only the count; any value except 0 - show the list of entities.',
                    'description' => 'The data until this date is shown. This argument is diregarded in the presence of the <i>date</i> argument.',
                ],
                [
                    'arg' => 'e_id',
                    'required' => FALSE,
                    'values' => 'any string',
                    'description' => 'Input for the entity search. If this is set then the start_date will be set to the beginning of history, type set to 7, opt set to 1, show_lost set to 1.',
                ],
                $this->addStdArgument('format'),
            ],
            'returns' => ['description' => ''],
            'examples' => [
                ['opt' =>[], 'title' => 'full history'],
                ['opt' => ['opt' => 2, 'start_date' => '2022-01-01', 'show_list' => 1], 'title' => 'show the list of withrown entities since 2022-01-01']
            ],
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }

        $entity_status = $this->opts['opt'];
        if ($entity_status != 1 && $entity_status != 2) {
            $entity_status = 0;
        }
        $startDate = $this->opts['start_date'];
        $endDate = $this->opts['end_date'];
        if ($this->opts['date']) {
            $startDate = $this->opts['date'];
            $endDate = $this->opts['date'];
        }
        if ($this->opts['e_id'] !== 'NOTSET') {
            $startDate = strtotime("2017-04-01");
            $entity_status = 1;
        }
        $edugain = new eduGAIN_history();
        $out = $edugain->getEntityHistory($startDate, $endDate, $this->opts['type'], $this->opts['show_list'], $this->opts['e_id'], $entity_status);
        return $out;
    }

    /*
      private function action_show_category_support() {
      $this->description([
      'header' => "Show the support for a given category in eduGAIN federtions (including SIRTFI which is not an entity category).",
      'supportedFormats' => ['json', 'csv', 'print_r'],
      'arguments' => [
          [
              'arg' => 'entity_cat',
              'required' => FALSE,
              'values' => ' 0 or none - no fileter; any valid entitycategory identiefier or https://refeds.org/sirtfi for SIRTFI',
              'default' => 0,
              'description' => 'limits the results to a given entity category identifier',
          ],
          [
              'arg' => 'format',
              'required' => FALSE,
              'default' => 'json',
              'values' => 'any supported format',
              'description' => '',
          ],
      ],
      'returns' => ['description' => '']
      ]);
      if($this->opts['help'] == 1) {
      return "";
      }

      $edugain = new eduGAIN();
      $out = $edugain->getCategorySupport($this->opts['entity_cat']);
      return $out;
      }

     */

    private function action_validator_warnings() {
        $this->description([
            'header' => "Show validator codes and corresponding messages. The codes correspond to bit numbers, therefore to map them to valkues shown in entity details one needs to shift 1 lest by the number shown in the code and then binary mask it with the number provided by entity details validator_status value.",
            'supportedFormats' => ['json', 'print_r'],
            'arguments' => [
                $this->addStdArgument('format'),
            ],
            'returns' => ['description' => 'pairs (code, description string)']
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }

        $edugain = new eduGAIN();
        return($edugain->getValidatorWarningsDict());
    }

    private function action_entity_attributes() {
        $this->description([
            'header' => "Show entity attribute dictionary.",
            'supportedFormats' => ['json', 'print_r'],
            'arguments' => [
                $this->addStdArgument('format'),
            ],
            'returns' => ['description' => 'associative array'],
            'examples' => [['opt' => [], 'title' => 'show the dictionary'],],
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }

        $edugain = new eduGAIN();
        return($edugain->getEntityAttributeDict());
    }
    
    private function action_link_tests() {
        $this->description([
            'header' => "Show the list of link test results. The result gives a cummulative view on links of given type. "
                    . 'If there are several docments listed under the same type then the result wil show failure uness all these documents are OK. '
                    . 'The last OK status will allso be understood as depending on the whole group.',
            'supportedFormats' => ['json', 'print_r'],
            'arguments' => [
                $this->addStdArgument('fed_id'),
                $this->addStdArgument('reg_auth'),
                $this->addStdArgument('date'),
                $this->addStdArgument('start_date'),
                $this->addStdArgument('end_date'),
                [
                    'arg' => 'only_errors',
                    'required' => FALSE,
                    'values' => '1 to indicate the option is set, any other value will be disregarded',
                    'description' => 'Show only tests that failed.',
                ],
                $this->addStdArgument('format'),
            ],
            'returns' => ['description' => 'Arrays of structured data - one entry per link type and test time.'],
            'examples' => [['opt' => [], 'title' => 'Status list from the last test'],],
        ]);

        if ($this->opts['help'] == 1) {
            return "";
        }
        $startDate = $this->opts['start_date'];
        $endDate = $this->opts['end_date'];
        if ($this->opts['date']) {
            $startDate = $this->opts['date'];
            $endDate = $this->opts['date'];
        }
        $edugain = new eduGAIN_history();
        $out = $edugain->getLinkHistory($startDate, $endDate, $this->fed_id, $this->opts['only_errors']);
        return $out;
    }
    
    private function action_link_tests_details() {
        $edugain = new eduGAIN_history();
        $this->description([
            'header' => "Show the detailed list of link test results. Contrary to the link_tests call, the last OK is shown per document",
            'supportedFormats' => ['json', 'print_r'],
            'arguments' => [
                $this->addStdArgument('fed_id'),
                $this->addStdArgument('reg_auth'),
                $this->addStdArgument('date'),
                $this->addStdArgument('start_date'),
                $this->addStdArgument('end_date'),
                [
                    'arg' => 'only_errors',
                    'required' => FALSE,
                    'values' => '1 to indicate the option is set, any other value will be disregarded',
                    'description' => 'Show only tests that failed.',
                ],
                [
                    'arg' => 'url_type',
                    'required' => FALSE,
                    'values' => implode(", ", $edugain->getUrlTypes()),
                    'description' => 'If set, limit the results to the given url_type. Values which are out of scope will be treated as empty',
                ],
                $this->addStdArgument('format'),
            ],
            'returns' => ['description' => 'Arrays of structured data - one entry per link type and test time.'],
            'examples' => [['opt' => [], 'title' => 'Status list from the last test'],],
        ]);
        if ($this->opts['help'] == 1) {
            return "";
        }
        $startDate = $this->opts['start_date'];
        $endDate = $this->opts['end_date'];
        if ($this->opts['date']) {
            $startDate = $this->opts['date'];
            $endDate = $this->opts['date'];
        }
        $out = $edugain->getLinkTestsDetails($startDate, $endDate, $this->fed_id, $this->opts['only_errors'], $this->opts['url_type']);
        return $out;
    }

    private function output($out) {
        if ($this->opts['help'] == 1) {
            $this->printDescription();
            return;
        }
        switch ($this->format) {
            case 'json':
                header('Content-type: application/json; charset=utf-8');
                header('Access-Control-Allow-Origin: *');
                print json_encode($out, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                break;
            case 'print_r':
                header('Content-type: text/html; charset=utf-8');
                print "<pre>";
                print_r($out);
                print "</pre>";
                break;
            case 'xml':
                header('Content-type: text/xml');
                print $out;
                break;
            case 'csv':
                header('Content-Type: text/csv; charset=utf-8');
                header('Content-Disposition: inline; filename="entites.csv"');
                print $out;
                break;
            case 'html':
                header('Content-type: text/html; charset=utf-8');
                print '<html xmlns="http://www.w3.org/1999/xhtml">';
                print $out;
                break;
            case 'png':
                header('Content-type: image/png');
                print $out;
                break;
            default:
                break;
        }
    }
    
    private function addStdArgument($argName, $required = FALSE) {
        $arg = $this->stdArguments[$argName];
        $arg['required'] = $required;
        return($arg);
    }


    private $stdArguments = [
        'format' => [
            'arg' => 'format',
            'required' => FALSE,
            'default' => 'json',
            'values' => 'any supported format',
            'description' => '',
            ],
        
        'fed_id' => [
            'arg' => 'fed_id',
            'required' => FALSE,
            'values' => 'a federation code (see <a href="api.php?action=list_fed_codes&help">list_fed_codes</a> for a list of available codes).',
            'description' => 'Limits the results to a single federation.',
            ],
        'date' => [
            'arg' => 'date',
            'required' => FALSE,
            'default' => 'today',
            'values' => 'Any date in the format YYYY-MM-DD is allowed.',
            'description' => 'The data for the single date is shown. If this argument is present then <i>start_date</i> and <i>end_date</i> are not taken into account.',
            ],
        'start_date' => [
            'arg' => 'start_date',
            'required' => FALSE,
            'values' => 'Any date in the format YYYY-MM-DD is allowed.',
            'description' => 'The data starting from this date is shown. This argument is diregarded in the presence of the <i>date</i> argument.',
            ],
        'end_date' => [
            'arg' => 'end_date',
            'required' => FALSE,
            'values' => 'Any date in the format YYYY-MM-DD is allowed.',
            'description' => 'The data until this date is shown. This argument is diregarded in the presence of the <i>date</i> argument.',
            ],
        'reg_auth' => [
            'arg' => 'reg_auth',
            'reuired' => FALSE,
            'values' => 'a federation registration authority URN',
            'description' => "Limits the results to a single federation. If fed_id argument is also present then it takes precedence over reg_auth",
            
        ],
    ];
        

}