HEX
Server: Apache/2.4.58 (Ubuntu)
System: Linux bsx-1-dev 6.8.0-101-generic #101-Ubuntu SMP PREEMPT_DYNAMIC Mon Feb 9 10:15:05 UTC 2026 x86_64
User: www-data (33)
PHP: 8.3.6
Disabled: NONE
Upload Files
File: /var/www/html/wp-content/plugins/memberpress/app/controllers/MeprApiCtrl.php
<?php

if (!defined('ABSPATH')) {
    die('You are not allowed to call this page directly.');
}

class MeprApiCtrl extends MeprBaseCtrl
{
    /**
     * Load hooks.
     *
     * @return void
     */
    public function load_hooks()
    {
        add_action('wp_ajax_nopriv_mepr_user', [$this, 'user']);
        add_action('wp_ajax_mepr_user', [$this, 'user']);
    }

    /**
     * GET user information
     *
     * @return void
     */
    public function user()
    {
        $user      = $this->user_auth();
        $mepr_user = new MeprUser($user->ID);

        $txns = $mepr_user->active_product_subscriptions('transactions');

        $filename = "{$mepr_user->user_login}-" . gmdate('Ymd');

        $struct                 = [];
        $struct['user']         = (array)$mepr_user->rec;
        $struct['transactions'] = [];
        $struct['timestamp']    = gmdate('Y-m-d H:i:s');

        foreach ($txns as $txn) {
            $txn_struct = (array)$txn->rec;
            unset($txn_struct['response']); // Exclude for security.
            unset($txn_struct['gateway']); // Exclude for security.
            unset($txn_struct['user_id']); // Redundant.
            $struct['transactions'][] = $txn_struct;
        }

        if (!isset($_REQUEST['fmt']) or $_REQUEST['fmt'] === 'json') {
            $this->render_json($struct, $filename);
        } elseif ($_REQUEST['fmt'] === 'xml') {
            $this->render_xml($struct, $filename);
        } elseif ($_REQUEST['fmt'] === 'csv') {
            $csv_struct = [];

            if (empty($txns)) {
                $csv_struct[] = array_keys($struct['user']);
                $csv_struct[] = array_values($struct['user']);
            }

            $txn_headers = array_keys($struct['transactions'][0]);

            foreach ($txn_headers as $i => $val) {
                $txn_headers[$i] = "transaction_{$val}";
            }

            $csv_struct[] = array_merge(
                array_keys($struct['user']),
                $txn_headers,
                ['timestamp']
            );

            foreach ($struct['transactions'] as $txn) {
                $csv_struct[] = array_merge(
                    array_values($struct['user']),
                    array_values($txn),
                    [$struct['timestamp']]
                );
            }

            $this->render_csv($csv_struct, $filename);
        }
    }

    /**
     * User auth.
     *
     * @return mixed
     */
    protected function user_auth()
    {
        if (!isset($_SERVER['PHP_AUTH_USER'])) {
            $this->unauthorized(__('No credentials have been provided.', 'memberpress'));
        } else {
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
            $user = wp_authenticate($_SERVER['PHP_AUTH_USER'] ?? '', $_SERVER['PHP_AUTH_PW'] ?? '');

            if (is_wp_error($user)) {
                $this->unauthorized($user->get_error_message());
            }

            return $user;
        }
    }

    /**
     * Unauthorized.
     *
     * @param  string $message The message.
     * @return void
     */
    protected function unauthorized($message)
    {
        header('WWW-Authenticate: Basic realm="' . MeprUtils::blogname() . '"');
        header('HTTP/1.0 401 Unauthorized');

        die(
            esc_html(
                sprintf(
                    // Translators: %s: error message.
                    __('UNAUTHORIZED: %s', 'memberpress'),
                    $message
                )
            )
        );
    }

    /**
     * Render json.
     *
     * @param  array  $struct   The struct.
     * @param  string $filename The filename.
     * @return void
     */
    protected function render_json($struct, $filename = '')
    {
        header('Content-Type: text/json');

        if (!$this->is_debug() and !empty($filename)) {
            header("Content-Disposition: attachment; filename=\"{$filename}.json\"");
        }

        die(json_encode($struct));
    }

    /**
     * Render xml.
     *
     * @param  array  $struct   The struct.
     * @param  string $filename The filename.
     * @return void
     */
    protected function render_xml($struct, $filename = '')
    {
        header('Content-Type: text/xml');

        if (!$this->is_debug() and !empty($filename)) {
            header("Content-Disposition: attachment; filename=\"{$filename}.xml\"");
        }

        die($this->to_xml($struct)); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    }

    /**
     * Render csv.
     *
     * @param  array  $struct   The struct.
     * @param  string $filename The filename.
     * @return void
     */
    protected function render_csv($struct, $filename = '')
    {
        if (!$this->is_debug()) {
            header('Content-Type: text/csv');

            if (!empty($filename)) {
                header("Content-Disposition: attachment; filename=\"{$filename}.csv\"");
            }
        }

        header('Content-Type: text/plain');

        die($this->to_csv($struct)); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    }

    /**
     * The main function for converting to an XML document.
     * Pass in a multi dimensional array and this recrusively loops through and builds up an XML document.
     *
     * @param array            $data             The data.
     * @param string           $root_node_name   What you want the root node to be - defaultsto data.
     * @param SimpleXMLElement $xml              Should only be used recursively.
     * @param string           $parent_node_name The parent node name.
     *
     * @return string XML
     */
    protected function to_xml($data, $root_node_name = 'memberpressData', $xml = null, $parent_node_name = '')
    {
        // Turn off compatibility mode as simple xml throws a wobbly if you don't.
        // DEPRECATED IN PHP 5.3
        // if(ini_get('zend.ze1_compatibility_mode') == 1)
        // ini_set('zend.ze1_compatibility_mode', 0);.
        if (is_null($xml)) {
            $xml = simplexml_load_string("<?xml version='1.0' encoding='utf-8'?><{$root_node_name} />");
        }

        // Loop through the data passed in.
        foreach ($data as $key => $value) {
            // No numeric keys in our XML please!
            if (is_numeric($key)) {
                if (empty($parent_node_name)) {
                    $key = 'unknownNode_' . (string)$key; // Make string key...
                } else {
                    $key = preg_replace('/s$/', '', $parent_node_name); // We assume that there's an 's' at the end of the string?
                }
            }

            $key = $this->camelize($key);

            // If there is another array found recursively call this function.
            if (is_array($value)) {
                $node = $xml->addChild($key);
                // Recursive call.
                $this->to_xml($value, $root_node_name, $node, $key);
            } else {
                // Add single node.
                $value = htmlentities($value);
                $xml->addChild($key, $value);
            }
        }

        // Pass back as string. Or simple xml object if you want!
        return $xml->asXML();
    }

    /**
     * Formats a line (passed as a fields array) as CSV and returns the CSV as a string.
     * Adapted from http://us3.php.net/manual/en/function.fputcsv.php#87120
     *
     * @param  array   $struct             The struct.
     * @param  string  $delimiter          The delimiter.
     * @param  string  $enclosure          The enclosure.
     * @param  boolean $enclose_all        The enclose all.
     * @param  boolean $null_to_mysql_null The null to mysql null.
     * @return string
     */
    public function to_csv(
        $struct,
        $delimiter = ',',
        $enclosure = '"',
        $enclose_all = false,
        $null_to_mysql_null = false
    ) {
        $delimiter_esc = preg_quote($delimiter, '/');
        $enclosure_esc = preg_quote($enclosure, '/');

        $csv      = '';
        $line_num = 0;
        foreach ($struct as $line) {
            $output = [];

            foreach ($line as $field) {
                if (is_null($field) and $null_to_mysql_null) {
                    $output[] = 'NULL';
                    continue;
                }

                // Enclose fields containing $delimiter, $enclosure or whitespace.
                if ($enclose_all or preg_match("/(?:{$delimiter_esc}|{$enclosure_esc}|\s)/", $field)) {
                    $output[] = $enclosure . str_replace($enclosure, $enclosure . $enclosure, $field) . $enclosure;
                } else {
                    $output[] = $field;
                }
            }

            $csv .= implode($delimiter, $output) . "\n";
            $line_num++;
        }

        return $csv;
    }

    /**
     * Camelize.
     *
     * @param  string $str The str.
     * @return string
     */
    protected function camelize($str)
    {
        // Level the playing field.
        $str = strtolower($str);
        // Replace dashes and/or underscores with spaces to prepare for ucwords.
        $str = preg_replace('/[-_]/', ' ', $str);
        // Ucwords bro ... uppercase the first letter of every word.
        $str = ucwords($str);
        // Now get rid of the spaces.
        $str = preg_replace('/ /', '', $str);
        // Lowercase the first character of the string.
        $str[0] = strtolower($str[0]);

        return $str;
    }

    /**
     * Is debug.
     *
     * @return boolean
     */
    protected function is_debug()
    {
        return (isset($_REQUEST['debug']) and (int)$_REQUEST['debug'] = 1);
    }
}