• File: elFinderVolumeBox.class.php
  • Full Path: /home/blwgracecity/jesusexp.org/wp-content/themes/twentytwentyfour/elFinderVolumeBox.class.php
  • File size: 16 KB
  • MIME-type: text/x-php
  • Charset: utf-8
<?php

/**
 * Simple elFinder driver for BoxDrive
 * Box.com API v2.0.
 *
 * @author Dmitry (dio) Levashov
 * @author Cem (discofever)
 **/
class elFinderVolumeBox extends elFinderVolumeDriver
{
    /**
     * Driver id
     * Must be started from letter and contains [a-z0-9]
     * Used as part of volume id.
     *
     * @var string
     **/
    protected $driverId = 'bd';

    /**
     * @var string The base URL for API requests
     */
    const API_URL = 'https://api.box.com/2.0';

    /**
     * @var string The base URL for authorization requests
     */
    const AUTH_URL = 'https://account.box.com/api/oauth2/authorize';

    /**
     * @var string The base URL for token requests
     */
    const TOKEN_URL = 'https://api.box.com/oauth2/token';

    /**
     * @var string The base URL for upload requests
     */
    const UPLOAD_URL = 'https://upload.box.com/api/2.0';

    /**
     * Fetch fields list.
     *
     * @var string
     */
    const FETCHFIELDS = 'type,id,name,created_at,modified_at,description,size,parent,permissions,file_version,shared_link';

    /**
     * Box.com token object.
     *
     * @var object
     **/
    protected $token = null;

    /**
     * Directory for tmp files
     * If not set driver will try to use tmbDir as tmpDir.
     *
     * @var string
     **/
    protected $tmp = '';

    /**
     * Net mount key.
     *
     * @var string
     **/
    public $netMountKey = '';

    /**
     * Thumbnail prefix.
     *
     * @var string
     **/
    private $tmbPrefix = '';

    /**
     * Path to access token file for permanent mount
     *
     * @var string
     */
    private $aTokenFile = '';

    /**
     * hasCache by folders.
     *
     * @var array
     **/
    protected $HasdirsCache = array();

    /**
     * Constructor
     * Extend options with required fields.
     *
     * @author Dmitry (dio) Levashov
     * @author Cem (DiscoFever)
     **/
    public function __construct()
    {
        $opts = array(
            'client_id' => '',
            'client_secret' => '',
            'accessToken' => '',
            'root' => 'Box.com',
            'path' => '/',
            'separator' => '/',
            'tmbPath' => '',
            'tmbURL' => '',
            'tmpPath' => '',
            'acceptedName' => '#^[^\\\/]+$#',
            'rootCssClass' => 'elfinder-navbar-root-box',
        );
        $this->options = array_merge($this->options, $opts);
        $this->options['mimeDetect'] = 'internal';
    }

    /*********************************************************************/
    /*                        ORIGINAL FUNCTIONS                         */
    /*********************************************************************/

    /**
     * Get Parent ID, Item ID, Parent Path as an array from path.
     *
     * @param string $path
     *
     * @return array
     */
    protected function _bd_splitPath($path)
    {
        $path = trim($path, '/');
        $pid = '';
        if ($path === '') {
            $id = '0';
            $parent = '';
        } else {
            $paths = explode('/', trim($path, '/'));
            $id = array_pop($paths);
            if ($paths) {
                $parent = '/' . implode('/', $paths);
                $pid = array_pop($paths);
            } else {
                $pid = '0';
                $parent = '/';
            }
        }

        return array($pid, $id, $parent);
    }

    /**
     * Obtains a new access token from OAuth. This token is valid for one hour.
     *
     * @param string $clientSecret The Box client secret
     * @param string $code         The code returned by Box after
     *                             successful log in
     * @param string $redirectUri  Must be the same as the redirect URI passed
     *                             to LoginUrl
     *
     * @return bool|object
     * @throws \Exception Thrown if this Client instance's clientId is not set
     * @throws \Exception Thrown if the redirect URI of this Client instance's
     *                    state is not set
     */
    protected function _bd_obtainAccessToken($client_id, $client_secret, $code)
    {
        if (null === $client_id) {
            return $this->setError('The client ID must be set to call obtainAccessToken()');
        }

        if (null === $client_secret) {
            return $this->setError('The client Secret must be set to call obtainAccessToken()');
        }

        if (null === $code) {
            return $this->setError('Authorization code must be set to call obtainAccessToken()');
        }

        $url = self::TOKEN_URL;

        $curl = curl_init();

        $fields = http_build_query(
            array(
                'client_id' => $client_id,
                'client_secret' => $client_secret,
                'code' => $code,
                'grant_type' => 'authorization_code',
            )
        );

        curl_setopt_array($curl, array(
            // General options.
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $fields,
            CURLOPT_URL => $url,
        ));

        $decoded = $this->_bd_curlExec($curl, true, array('Content-Length: ' . strlen($fields)));

        $res = (object)array(
            'expires' => time() + $decoded->expires_in - 30,
            'initialToken' => '',
            'data' => $decoded
        );
        if (!empty($decoded->refresh_token)) {
            $res->initialToken = md5($client_id . $decoded->refresh_token);
        }
        return $res;
    }

    /**
     * Get token and auto refresh.
     *
     * @return true|string error message
     * @throws Exception
     */
    protected function _bd_refreshToken()
    {
        if (!property_exists($this->token, 'expires') || $this->token->expires < time()) {
            if (!$this->options['client_id']) {
                $this->options['client_id'] = ELFINDER_BOX_CLIENTID;
            }

            if (!$this->options['client_secret']) {
                $this->options['client_secret'] = ELFINDER_BOX_CLIENTSECRET;
            }

            if (empty($this->token->data->refresh_token)) {
                throw new \Exception(elFinder::ERROR_REAUTH_REQUIRE);
            } else {
                $refresh_token = $this->token->data->refresh_token;
                $initialToken = $this->_bd_getInitialToken();
            }

            $lock = '';
            $aTokenFile = $this->aTokenFile? $this->aTokenFile : $this->_bd_getATokenFile();
            if ($aTokenFile && is_file($aTokenFile)) {
                $lock = $aTokenFile . '.lock';
                if (file_exists($lock)) {
                    // Probably updating on other instance
                    return true;
                }
                touch($lock);
                $GLOBALS['elFinderTempFiles'][$lock] = true;
            }

            $postData = array(
                'client_id' => $this->options['client_id'],
                'client_secret' => $this->options['client_secret'],
                'grant_type' => 'refresh_token',
                'refresh_token' => $refresh_token
            );

            $url = self::TOKEN_URL;

            $curl = curl_init();

            curl_setopt_array($curl, array(
                // General options.
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_POST => true, // i am sending post data
                CURLOPT_POSTFIELDS => http_build_query($postData),
                CURLOPT_URL => $url,
            ));

            $decoded = $error = '';
            try {
                $decoded = $this->_bd_curlExec($curl, true, array(), $postData);
            } catch (Exception $e) {
                $error = $e->getMessage();
            }
            if (!$decoded && !$error) {
                $error = 'Tried to renew the access token, but did not get a response from the Box server.';
            }
            if ($error) {
                $lock && unlink($lock);
                throw new \Exception('Box access token update failed. ('.$error.') If this message appears repeatedly, please notify the administrator.');
            }

            if (empty($decoded->access_token)) {
                if ($aTokenFile) {
                    if (is_file($aTokenFile)) {
                        unlink($aTokenFile);
                    }
                }
                $err = property_exists($decoded, 'error')? ' ' . $decoded->error : '';
                $err .= property_exists($decoded, 'error_description')? ' ' . $decoded->error_description : '';
                throw new \Exception($err? $err : elFinder::ERROR_REAUTH_REQUIRE);
            }

            $token = (object)array(
                'expires' => time() + $decoded->expires_in - 300,
                'initialToken' => $initialToken,
                'data' => $decoded,
            );

            $this->token = $token;
            $json = json_encode($token);

            if (!empty($decoded->refresh_token)) {
                if (empty($this->options['netkey']) && $aTokenFile) {
                    file_put_contents($aTokenFile, json_encode($token), LOCK_EX);
                    $this->options['accessToken'] = $json;
                } else if (!empty($this->options['netkey'])) {
                    // OAuth2 refresh token can be used only once,
                    // so update it if it is the same as the token file
                    if ($aTokenFile && is_file($aTokenFile)) {
                        if ($_token = json_decode(file_get_contents($aTokenFile))) {
                            if ($_token->data->refresh_token === $refresh_token) {
                                file_put_contents($aTokenFile, $json, LOCK_EX);
                            }
                        }
                    }
                    $this->options['accessToken'] = $json;
                    // update session value
                    elFinder::$instance->updateNetVolumeOption($this->options['netkey'], 'accessToken', $json);
                    $this->session->set('BoxTokens', $token);
                } else {
                    throw new \Exception(ERROR_CREATING_TEMP_DIR);
                }
            }
            $lock && unlink($lock);
        }

        return true;
    }

    /**
     * Creates a base cURL object which is compatible with the Box.com API.
     *
     * @param array $options cURL options
     *
     * @return resource A compatible cURL object
     */
    protected function _bd_prepareCurl($options = array())
    {
        $curl = curl_init();

        $defaultOptions = array(
            // General options.
            CURLOPT_RETURNTRANSFER => true,
        );

        curl_setopt_array($curl, $options + $defaultOptions);

        return $curl;
    }

    /**
     * Creates a base cURL object which is compatible with the Box.com API.
     *
     * @param      $url
     * @param bool $contents
     *
     * @return boolean|array
     * @throws Exception
     */
    protected function _bd_fetch($url, $contents = false)
    {
        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

        if ($contents) {
            return $this->_bd_curlExec($curl, false);
        } else {
            $result = $this->_bd_curlExec($curl);

            if (isset($result->entries)) {
                $res = $result->entries;
                $cnt = count($res);
                $total = $result->total_count;
                $offset = $result->offset;
                $single = ($result->limit == 1) ? true : false;
                if (!$single && $total > ($offset + $cnt)) {
                    $offset = $offset + $cnt;
                    if (strpos($url, 'offset=') === false) {
                        $url .= '&offset=' . $offset;
                    } else {
                        $url = preg_replace('/^(.+?offset=)\d+(.*)$/', '${1}' . $offset . '$2', $url);
                    }
                    $more = $this->_bd_fetch($url);
                    if (is_array($more)) {
                        $res = array_merge($res, $more);
                    }
                }

                return $res;
            } else {
                if (isset($result->type) && $result->type === 'error') {
                    return false;
                } else {
                    return $result;
                }
            }
        }
    }

    /**
     * Call curl_exec().
     *
     * @param resource    $curl
     * @param bool|string $decodeOrParent
     * @param array       $headers
     *
     * @throws \Exception
     * @return mixed
     */
    protected function _bd_curlExec($curl, $decodeOrParent = true, $headers = array(), $postData = array())
    {
        if ($this->token) {
            $headers = array_merge(array(
                'Authorization: Bearer ' . $this->token->data->access_token,
            ), $headers);
        }

        $result = elFinder::curlExec($curl, array(), $headers, $postData);

        if (!$decodeOrParent) {
            return $result;
        }

        $decoded = json_decode($result);

        if ($error = !empty($decoded->error_code)) {
            $errmsg = $decoded->error_code;
            if (!empty($decoded->message)) {
                $errmsg .= ': ' . $decoded->message;
            }
            throw new \Exception($errmsg);
        } else if ($error = !empty($decoded->error)) {
            $errmsg = $decoded->error;
            if (!empty($decoded->error_description)) {
                $errmsg .= ': ' . $decoded->error_description;
            }
            throw new \Exception($errmsg);
        }

        // make catch
        if ($decodeOrParent && $decodeOrParent !== true) {
            $raws = null;
            if (isset($decoded->entries)) {
                $raws = $decoded->entries;
            } elseif (isset($decoded->id)) {
                $raws = array($decoded);
            }
            if ($raws) {
                foreach ($raws as $raw) {
                    if (isset($raw->id)) {
                        $stat = $this->_bd_parseRaw($raw);
                        $itemPath = $this->_joinPath($decodeOrParent, $raw->id);
                        $this->updateCache($itemPath, $stat);
                    }
                }
            }
        }

        return $decoded;
    }

    /**
     * Drive query and fetchAll.
     *
     * @param      $itemId
     * @param bool $fetch_self
     * @param bool $recursive
     *
     * @return bool|object
     * @throws Exception
     */
    protected function _bd_query($itemId, $fetch_self = false, $recursive = false)
    {
        $result = [];

        if (null === $itemId) {
            $itemId = '0';
        }

        if ($fetch_self) {
            $path = '/folders/' . $itemId . '?fields=' . self::FETCHFIELDS;
        } else {
            $path = '/folders/' . $itemId . '/items?limit=1000&fields=' . self::FETCHFIELDS;
        }

        $url = self::API_URL . $path;

        if ($recursive) {
            foreach ($this->_bd_fetch($url) as $file) {
                if ($file->type == 'folder') {
                    $result[] = $file;
                    $result = array_merge($result, $this->_bd_query($file->id, $fetch_self = false, $recursive = true));
                } elseif ($file->type == 'file') {
                    $result[] = $file;
                }
            }
        } else {
            $result = $this->_bd_fetch($url);
            if ($fetch_self && !$result) {
                $path = '/files/' . $itemId . '?fields=' . self::FETCHFIELDS;
                $url = self::API_URL . $path;
                $result = $this->_bd_fetch($url);
            }
        }

        return $result;
    }

    /**
     * Get dat(box metadata) from Box.com.
     *
     * @param string $path
     *
     * @return object box metadata
     * @throws Exception
     */
    protected function _bd_getRawItem($path)
    {
        if ($path == '/') {
            return $this->_bd_query('0', $fetch_self = true);
        }

        list(, $itemId) = $this->_bd_splitPath($path);

        try {
            return $this->_bd_query($itemId, $fetch_self = true);
        } catch (Exception $e) {
            $empty = new stdClass;
            return $empty;
        }
    }

    /**
     * Parse line from box metadata output and return f