From 66d82b4e492586728ee33d7ff75478e1ca48693e Mon Sep 17 00:00:00 2001 From: nmatuschek Date: Tue, 18 Sep 2018 09:00:08 +0200 Subject: [PATCH] Added Feature 'enhanced security for adobe connect 9.0.4' --- autoload.php | 4 + classes/class.ilAdobeConnectConfigGUI.php | 15 +- classes/class.ilAdobeConnectDfnXMLAPI.php | 163 +-- classes/class.ilAdobeConnectPlugin.php | 18 + classes/class.ilAdobeConnectQuota.php | 3 +- classes/class.ilAdobeConnectUserUtil.php | 56 +- classes/class.ilAdobeConnectXMLAPI.php | 1024 +++++++------- classes/class.ilObjAdobeConnect.php | 249 +--- classes/class.ilObjAdobeConnectGUI.php | 16 +- lang/ilias_de.lang | 4 +- lang/ilias_en.lang | 4 +- libs/composer/composer.json | 5 + libs/composer/composer.lock | 68 + libs/composer/vendor/autoload.php | 7 + libs/composer/vendor/composer/ClassLoader.php | 445 +++++++ libs/composer/vendor/composer/LICENSE | 21 + .../vendor/composer/autoload_classmap.php | 9 + .../vendor/composer/autoload_namespaces.php | 10 + .../vendor/composer/autoload_psr4.php | 9 + .../vendor/composer/autoload_real.php | 52 + .../vendor/composer/autoload_static.php | 26 + libs/composer/vendor/composer/installed.json | 54 + .../vendor/nategood/httpful/.gitignore | 5 + .../vendor/nategood/httpful/.travis.yml | 17 + .../vendor/nategood/httpful/LICENSE.txt | 7 + .../vendor/nategood/httpful/README.md | 228 ++++ .../vendor/nategood/httpful/bootstrap.php | 4 + libs/composer/vendor/nategood/httpful/build | 51 + .../vendor/nategood/httpful/composer.json | 27 + .../nategood/httpful/examples/freebase.php | 12 + .../nategood/httpful/examples/github.php | 9 + .../nategood/httpful/examples/override.php | 44 + .../nategood/httpful/examples/showclix.php | 24 + .../httpful/src/Httpful/Bootstrap.php | 97 ++ .../Exception/ConnectionErrorException.php | 7 + .../src/Httpful/Handlers/CsvHandler.php | 51 + .../src/Httpful/Handlers/FormHandler.php | 30 + .../src/Httpful/Handlers/JsonHandler.php | 42 + .../Httpful/Handlers/MimeHandlerAdapter.php | 54 + .../httpful/src/Httpful/Handlers/README.md | 44 + .../src/Httpful/Handlers/XHtmlHandler.php | 15 + .../src/Httpful/Handlers/XmlHandler.php | 152 +++ .../nategood/httpful/src/Httpful/Http.php | 86 ++ .../nategood/httpful/src/Httpful/Httpful.php | 47 + .../nategood/httpful/src/Httpful/Mime.php | 60 + .../nategood/httpful/src/Httpful/Proxy.php | 16 + .../nategood/httpful/src/Httpful/Request.php | 1186 +++++++++++++++++ .../nategood/httpful/src/Httpful/Response.php | 195 +++ .../httpful/src/Httpful/Response/Headers.php | 88 ++ .../httpful/tests/Httpful/HttpfulTest.php | 604 +++++++++ .../httpful/tests/Httpful/requestTest.php | 21 + .../httpful/tests/bootstrap-server.php | 42 + .../vendor/nategood/httpful/tests/phpunit.xml | 16 + .../nategood/httpful/tests/static/test.json | 1 + .../nategood/httpful/tests/test_image.jpg | Bin 0 -> 52666 bytes 55 files changed, 4691 insertions(+), 853 deletions(-) create mode 100644 autoload.php create mode 100644 libs/composer/composer.json create mode 100644 libs/composer/composer.lock create mode 100644 libs/composer/vendor/autoload.php create mode 100644 libs/composer/vendor/composer/ClassLoader.php create mode 100644 libs/composer/vendor/composer/LICENSE create mode 100644 libs/composer/vendor/composer/autoload_classmap.php create mode 100644 libs/composer/vendor/composer/autoload_namespaces.php create mode 100644 libs/composer/vendor/composer/autoload_psr4.php create mode 100644 libs/composer/vendor/composer/autoload_real.php create mode 100644 libs/composer/vendor/composer/autoload_static.php create mode 100644 libs/composer/vendor/composer/installed.json create mode 100644 libs/composer/vendor/nategood/httpful/.gitignore create mode 100644 libs/composer/vendor/nategood/httpful/.travis.yml create mode 100644 libs/composer/vendor/nategood/httpful/LICENSE.txt create mode 100644 libs/composer/vendor/nategood/httpful/README.md create mode 100644 libs/composer/vendor/nategood/httpful/bootstrap.php create mode 100755 libs/composer/vendor/nategood/httpful/build create mode 100644 libs/composer/vendor/nategood/httpful/composer.json create mode 100644 libs/composer/vendor/nategood/httpful/examples/freebase.php create mode 100644 libs/composer/vendor/nategood/httpful/examples/github.php create mode 100644 libs/composer/vendor/nategood/httpful/examples/override.php create mode 100644 libs/composer/vendor/nategood/httpful/examples/showclix.php create mode 100644 libs/composer/vendor/nategood/httpful/src/Httpful/Bootstrap.php create mode 100644 libs/composer/vendor/nategood/httpful/src/Httpful/Exception/ConnectionErrorException.php create mode 100644 libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/CsvHandler.php create mode 100644 libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/FormHandler.php create mode 100644 libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/JsonHandler.php create mode 100644 libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/MimeHandlerAdapter.php create mode 100644 libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/README.md create mode 100644 libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/XHtmlHandler.php create mode 100644 libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/XmlHandler.php create mode 100644 libs/composer/vendor/nategood/httpful/src/Httpful/Http.php create mode 100644 libs/composer/vendor/nategood/httpful/src/Httpful/Httpful.php create mode 100644 libs/composer/vendor/nategood/httpful/src/Httpful/Mime.php create mode 100644 libs/composer/vendor/nategood/httpful/src/Httpful/Proxy.php create mode 100644 libs/composer/vendor/nategood/httpful/src/Httpful/Request.php create mode 100644 libs/composer/vendor/nategood/httpful/src/Httpful/Response.php create mode 100644 libs/composer/vendor/nategood/httpful/src/Httpful/Response/Headers.php create mode 100644 libs/composer/vendor/nategood/httpful/tests/Httpful/HttpfulTest.php create mode 100644 libs/composer/vendor/nategood/httpful/tests/Httpful/requestTest.php create mode 100644 libs/composer/vendor/nategood/httpful/tests/bootstrap-server.php create mode 100644 libs/composer/vendor/nategood/httpful/tests/phpunit.xml create mode 100644 libs/composer/vendor/nategood/httpful/tests/static/test.json create mode 100644 libs/composer/vendor/nategood/httpful/tests/test_image.jpg diff --git a/autoload.php b/autoload.php new file mode 100644 index 0000000..84afa4e --- /dev/null +++ b/autoload.php @@ -0,0 +1,4 @@ +setInfo($this->getPluginObject()->txt('schedule_lead_time_info')); $this->form->addItem($form_lead_time); + $form_security_mode = new ilCheckboxInputGUI($this->getPluginObject()->txt('enhanced_security_mode'), 'enhanced_security_mode'); + $form_security_mode->setInfo($this->pluginObj->txt('enhanced_security_mode_info')); + $this->form->addItem($form_security_mode); + $head_line = new ilFormSectionHeaderGUI(); $head_line->setTitle($this->getPluginObject()->txt('presentation_server_settings')); $this->form->addItem($head_line); @@ -219,6 +223,7 @@ private function getAdobeSettingsValues() $values['x_user_id'] = ilAdobeConnectServer::getSetting('x_user_id')? ilAdobeConnectServer::getSetting('x_user_id') : 'x_user_id'; + $values['enhanced_security_mode'] = ilAdobeConnectServer::getSetting('enhanced_security_mode', 0); $this->form->setValuesByArray($values); } @@ -297,6 +302,8 @@ public function saveAdobeSettings() } ilAdobeConnectServer::setSetting($key, trim($value)); } + ilAdobeConnectServer::setSetting('enhanced_security_mode', (int)$this->form->getInput('enhanced_security_mode')); + ilAdobeConnectServer::setSetting('auth_mode_switchaai_account_type',serialize($this->form->getInput('auth_mode_switchaai_account_type'))); ilAdobeConnectServer::_getInstance()->commitSettings(); @@ -307,14 +314,8 @@ public function saveAdobeSettings() if(ilAdobeConnectServer::getSetting('user_assignment_mode') != ilAdobeConnectServer::ASSIGN_USER_SWITCH) { $xmlAPI = ilXMLApiFactory::getApiByAuthMode(); - $session = $xmlAPI->getBreezeSession(); - - if(!$session) - { - throw new ilException('err_invalid_server'); - } - if(!$xmlAPI->login(trim($this->form->getInput('login')), trim($this->form->getInput('password')), $session)) + if(!$xmlAPI->login(trim($this->form->getInput('login')), trim($this->form->getInput('password')), null)) { throw new ilException('err_authentication_failed'); } diff --git a/classes/class.ilAdobeConnectDfnXMLAPI.php b/classes/class.ilAdobeConnectDfnXMLAPI.php index 4f62bc0..1d05b15 100644 --- a/classes/class.ilAdobeConnectDfnXMLAPI.php +++ b/classes/class.ilAdobeConnectDfnXMLAPI.php @@ -42,22 +42,22 @@ protected function getApiUrl($params) * @param string $session * @return bool */ - public function addUser($login, $email, $pass, $first_name, $last_name, $session) + public function addUser($login, $email, $pass, $first_name, $last_name, $session = null) { global $DIC; $ilLog = $DIC->logger()->root(); $url = $this->getApiUrl(array( - 'action' => 'lms-user-create', + 'action' => 'lms-user-create', 'login' => $login, - 'first-name' => $first_name, - 'last-name' => $last_name, - 'session' => $session + 'first-name' => $first_name, + 'last-name' => $last_name, + 'session' => $session )); $ilLog->write("addUser URL: ". $url); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); if($xml->status['code'] == 'ok') { @@ -79,7 +79,7 @@ public function addUser($login, $email, $pass, $first_name, $last_name, $session * @param string $session * @return bool|string */ - public function searchUser($login, $session) + public function searchUser($login, $session = NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -87,17 +87,18 @@ public function searchUser($login, $session) $url = $this->getApiUrl(array( 'login' => $login, 'action' => 'lms-user-exists', - 'session' => $session + 'session' => $session )); - $xml = simplexml_load_file($url); - - if($xml->status['code'] == 'ok') + $xml = $this->sendRequest($url); + if($xml instanceof \SimpleXMLElement) { - $list = $xml->{'principal-list'}; - - $id = (string)$list->principal['principal-id']; + if($xml->status['code'] == 'ok') + { + $list = $xml->{'principal-list'}; + $id = (string)$list->principal['principal-id']; - return $id; + return $id; + } } else { @@ -126,18 +127,10 @@ public function externalLogin($user = null, $pass = null, $session = null ) $url = $this->getApiUrl(array( 'action' => 'lms-user-login', 'login' => $user, - 'session' => $session + 'session' => $session )); - $context = array( - 'http' => array('timeout' => 4), - 'https' => array('timeout' => 4) - ); - - $ctx = $this->proxy($context); - $xml_string = file_get_contents($url, false, $ctx); - $xml = simplexml_load_string($xml_string); - + $xml = $this->sendRequest($url); if($xml->status['code'] == 'ok') { return (string)$xml->cookie; @@ -149,76 +142,68 @@ public function externalLogin($user = null, $pass = null, $session = null ) return false; } - /** - * @param string $user - * @param string $pass - * @param string $session - * @return bool - */ - public function login($user, $pass, $session) - { - global $DIC; - $ilLog = $DIC->logger()->root(); - $lng = $DIC->language(); - - if(isset(self::$loginsession_cache[$session])) - { - return true; - } - - $url = $this->getApiUrl(array( - 'action' => 'login', - 'login' => $user, - 'password' => $pass, - 'session' => $session - )); - - $context = array( - 'http' => array( - 'timeout' => 4 - ), - 'https' => array( - 'timeout' => 4 - ) - ); - - $ctx = $this->proxy($context); - $xml_string = file_get_contents($url, false, $ctx); - $xml = simplexml_load_string($xml_string); - - if($xml->status['code'] == 'ok') - { - self::$loginsession_cache[$session] = true; - return true; - } - else - { - unset(self::$loginsession_cache[$session]); - $ilLog->write('AdobeConnect login Request: '.$url); - if($xml) - { - $ilLog->write('AdobeConnect login Response: ' . $xml->asXML()); - } - $ilLog->write('AdobeConnect login failed: '.$user); - ilUtil::sendFailure($lng->txt('login_failed')); - return false; - } - } - - /** - * @param String $session - * @return bool|void - */ - public function logout($session) - { - } +// /** +// * @param string $user +// * @param string $pass +// * @param string $session +// * @return bool +// */ +// public function login($user, $pass, $session) +// { +// global $DIC; +// $ilLog = $DIC->logger()->root(); +// $lng = $DIC->language(); +// +// if(isset(self::$loginsession_cache[$session])) +// { +// return true; +// } +// +// $url = $this->getApiUrl(array( +// 'action' => 'login', +// 'login' => $user, +// 'password' => $pass, +// 'session' => $session +// )); +// +// $context = array( +// 'http' => array( +// 'timeout' => 4 +// ), +// 'https' => array( +// 'timeout' => 4 +// ) +// ); +// +// $ctx = $this->proxy($context); +// $xml_string = file_get_contents($url, false, $ctx); +// $xml = simplexml_load_string($xml_string); +// +// if($xml->status['code'] == 'ok') +// { +// self::$loginsession_cache[$session] = true; +// return true; +// } +// else +// { +// unset(self::$loginsession_cache[$session]); +// $ilLog->write('AdobeConnect login Request: '.$url); +// if($xml) +// { +// $ilLog->write('AdobeConnect login Response: ' . $xml->asXML()); +// } +// $ilLog->write('AdobeConnect login failed: '.$user); +// ilUtil::sendFailure($lng->txt('login_failed')); +// return false; +// } +// } /** * @param String $login * @param String $session * @return null|String */ - public function getPrincipalId($login, $session) + public function getPrincipalId($login, $session = null) { return $this->searchUser($login, $session); } diff --git a/classes/class.ilAdobeConnectPlugin.php b/classes/class.ilAdobeConnectPlugin.php index 5c0022f..7d33abe 100755 --- a/classes/class.ilAdobeConnectPlugin.php +++ b/classes/class.ilAdobeConnectPlugin.php @@ -20,6 +20,24 @@ public function getPluginName() return "AdobeConnect"; } + /** + * @inheritdoc + */ + protected function init() + { + parent::init(); + + $this->registerAutoloader(); + } + + /** + * Registers the plugin autoloader + */ + private function registerAutoloader() + { + require_once dirname(__FILE__) . '/../autoload.php'; + } + /** * @param $string a_type * @param $string $a_size diff --git a/classes/class.ilAdobeConnectQuota.php b/classes/class.ilAdobeConnectQuota.php index 2a404a2..cf91b2c 100755 --- a/classes/class.ilAdobeConnectQuota.php +++ b/classes/class.ilAdobeConnectQuota.php @@ -31,8 +31,7 @@ public function __construct() private function fetchCurrentMeetings() { $api = ilXMLApiFactory::getApiByAuthMode(); - $session = $api->getAdminSession(); - return $api->getActiveScos($session); + return $api->getActiveScos(); } private function buildCurrentMeetings(array $data) diff --git a/classes/class.ilAdobeConnectUserUtil.php b/classes/class.ilAdobeConnectUserUtil.php index d1717f4..568e56d 100755 --- a/classes/class.ilAdobeConnectUserUtil.php +++ b/classes/class.ilAdobeConnectUserUtil.php @@ -147,11 +147,6 @@ private function ensureAdobeConnectAccountExistance() public function ensureUserFolderExistance($a_xavc_login = "") { $xmlAPI = ilXMLApiFactory::getApiByAuthMode(); - $session = $xmlAPI->getBreezeSession(); - - $instance = ilAdobeConnectServer::_getInstance(); - $login = $instance->getLogin(); - $pass = $instance->getPasswd(); if(ilAdobeConnectServer::getSetting('use_user_folders') == 1) { @@ -164,29 +159,19 @@ public function ensureUserFolderExistance($a_xavc_login = "") $xavc_login = $this->getXAVCLogin(); } - if ($session != NULL && $xmlAPI->login($login, $pass, $session)) + $folder_id = $xmlAPI->lookupUserFolderId($xavc_login); + + if($folder_id == NULL) { - $folder_id = $xmlAPI->lookupUserFolderId($xavc_login, $session); - - if($folder_id == NULL) - { - $folder_id = $xmlAPI->createUserFolder($xavc_login, $session); - } - - return $folder_id; + $folder_id = $xmlAPI->createUserFolder($xavc_login); } + + return $folder_id; } else { - if ($session != NULL && $xmlAPI->login($login, $pass, $session)) - { - $folder_id = $xmlAPI->getShortcuts('my-meetings', $session); + $folder_id = $xmlAPI->getShortcuts('my-meetings'); return $folder_id; - } - else - { - return NULL; - } } } @@ -259,19 +244,11 @@ public function getXAVCLogin() public function addUser() { $xmlAPI = ilXMLApiFactory::getApiByAuthMode(); - $session = $xmlAPI->getBreezeSession(); - - $instance = ilAdobeConnectServer::_getInstance(); - $login = $instance->getLogin(); - $pass = $instance->getPasswd(); $user_pass = $this->generatePass(); - if ($session != NULL && $xmlAPI->login($login, $pass, $session)) - { - $xmlAPI->addUser($this->getXAVCLogin(), $this->email, $user_pass, $this->first_name, $this->last_name, $session); - $xmlAPI->logout($session); - } + $xmlAPI->addUser($this->getXAVCLogin(), $this->email, $user_pass, $this->first_name, $this->last_name, null); + $xmlAPI->logout(null); } /**Search user on the Adobe Connect server @@ -290,19 +267,11 @@ public function searchUser($a_xavc_login = '') $xavc_login = $this->getXAVCLogin(); } $xmlAPI = ilXMLApiFactory::getApiByAuthMode(); - $session = $xmlAPI->getBreezeSession(); - $instance = ilAdobeConnectServer::_getInstance(); - $login = $instance->getLogin(); - $pass = $instance->getPasswd(); - - if ($session != NULL && $xmlAPI->login($login, $pass, $session)) - { - $search = $xmlAPI->searchUser($xavc_login, $session); - $xmlAPI->logout($session); + $search = $xmlAPI->searchUser($xavc_login, null); +// $xmlAPI->logout(null); return $search; } - } /** * Log in user on the Adobe Connect server @@ -311,9 +280,8 @@ public function searchUser($a_xavc_login = '') public function loginUser() { $xmlAPI = ilXMLApiFactory::getApiByAuthMode(); - $session = $xmlAPI->getBreezeSession(); - return $xmlAPI->externalLogin($this->getXAVCLogin(), null, $session); + return $xmlAPI->externalLogin($this->getXAVCLogin()); } /** diff --git a/classes/class.ilAdobeConnectXMLAPI.php b/classes/class.ilAdobeConnectXMLAPI.php index f0eab30..0ea2577 100644 --- a/classes/class.ilAdobeConnectXMLAPI.php +++ b/classes/class.ilAdobeConnectXMLAPI.php @@ -4,10 +4,16 @@ /** * Connect to Adobe Connect API +* * @author Nadia Matuschek */ class ilAdobeConnectXMLAPI { + /** + * + */ + const XAVC_COOKIE_PATH = CLIENT_DATA_DIR.'/temp/xavc/'; + /** * Adobe Connect server * @var String @@ -36,15 +42,14 @@ class ilAdobeConnectXMLAPI * @var null */ protected $auth_mode = null; - /** - * @var array - */ protected static $loginsession_cache = array(); /** * @var array */ protected static $scocontent_cache = array(); - + + protected $enhanced_security_mode = false; + /** * ilAdobeConnectXMLAPI constructor. */ @@ -55,92 +60,69 @@ public function __construct() $this->port = $this->adcInfo->getPort(); $this->x_user_id = $this->adcInfo->getXUserId(); $this->auth_mode = $this->adcInfo->getAuthMode(); + $this->enhanced_security_mode = (bool)ilAdobeConnectServer::getSetting('enhanced_security_mode'); $this->proxy(); + + ilUtil::makeDirParents(self::XAVC_COOKIE_PATH); + $this->generateAdminSessionCookie(); + } + + public function generateAdminSessionCookie() + { + $this->getBreezeSession(true); + $this->login($this->adcInfo->getLogin(), $this->adcInfo->getPasswd()); + + if(ilAdobeConnectServer::getSetting('enhanced_security_mode')) + $this->getBreezeSession(false); } /** + * @param $login * @return null|string */ - public function getXUserId() + public function generateUserSessionCookie($login) { - return $this->x_user_id; + $this->getBreezeSession(true); + $this->externalLogin($login); + return $this->getBreezeSession(false); } - - /** - * @return null|string - */ - public function getAdminSession() + + public function getXUserId() { - $session = $this->getBreezeSession(); - - if(!$session) - { - /** - * @todo introduce exception - */ - return null; - } - - $success = $this->login($this->adcInfo->getLogin(), $this->adcInfo->getPasswd(), $session); - - if($success) - { - return $session; - } - else - { - /** - * @todo introduce exception - */ - return null; - } + return $this->x_user_id; } - + /** * Logs in user on the Adobe Connect server. The session id is caches until the * logout function is called with the session id. + * * @param String $user Adobe Connect user login * @param String $pass Adobe Connect user password * @param String $session Session id * @return boolean return true if everything is ok */ - public function login($user, $pass, $session) + public function login($user, $pass, $session = NULL) { global $DIC; $lng = $DIC->language(); $ilLog = $DIC->logger()->root(); - if(isset(self::$loginsession_cache[$session]) && self::$loginsession_cache[$session]) - { - return self::$loginsession_cache[$session]; - } - - if(isset($user, $pass, $session)) + if(isset($user, $pass)) { $url = $this->getApiUrl(array( 'action' => 'login', 'login' => $user, - 'password' => $pass, - 'session' => $session - )); - - $context = (array( - 'http' => array('timeout' => 4), - 'https' => array('timeout' => 4) + 'password' => $pass )); - $ctx = $this->proxy($context); - $xml_string = file_get_contents($url, false, $ctx); - $xml = simplexml_load_string($xml_string); + $xml = $this->sendRequest($url); if($xml->status['code'] == 'ok') { - self::$loginsession_cache[$session] = true; return true; } else { - unset(self::$loginsession_cache[$session]); $ilLog->write('AdobeConnect login Request: ' . $url); if($xml) { @@ -153,7 +135,6 @@ public function login($user, $pass, $session) } else { - unset(self::$loginsession_cache[$session]); $ilLog->write('AdobeConnect login failed due to missing login credentials ...'); ilUtil::sendFailure($lng->txt('err_wrong_login')); return false; @@ -168,18 +149,18 @@ public function login($user, $pass, $session) */ public function changeUserPassword($username, $newPassword) { - $user_id = $this->searchUser($username, $this->getAdminSession()); + $user_id = $this->searchUser($username,null); if($user_id) { $url = $this->getApiUrl(array( 'action' => 'user-update-pwd', - 'session' => $this->getAdminSession(), 'password' => $newPassword, 'password-verify' => $newPassword, 'user-id' => $user_id )); - $xml = simplexml_load_file($url); + + $xml = $this->sendRequest($url); return $xml instanceof SimpleXMLElement && $xml->status['code'] == 'ok'; } @@ -213,24 +194,16 @@ public function externalLogin($user = null, $pass = null, $session = null) * @param String $session Session id * @return boolean return true if everything is ok */ - public function logout($session) + public function logout($session = NULL) { global $DIC; $ilLog = $DIC->logger()->root(); $url = $this->getApiUrl(array( - 'action' => 'logout', - 'session' => $session + 'action' => 'logout' )); - $xml = simplexml_load_file($url); - - if($session == self::$breeze_session) - { - self::$breeze_session = null; - } - - unset(self::$loginsession_cache[$session]); + $xml = $this->sendRequest($url); if($xml->status['code'] == "ok") { @@ -263,25 +236,16 @@ public function getBreezeSession($useCache = true) } $url = $this->getApiUrl(array('action' => 'common-info')); - - $context = array( - 'http' => array('timeout' => 4), - 'https' => array('timeout' => 4) - ); - $ctx = $this->proxy($context); - $xml_string = file_get_contents($url, false, $ctx); - $xml = simplexml_load_string($xml_string); + $xml = $this->sendRequest($url); if($xml && $xml->common->cookie != "") { $session = (string)$xml->common->cookie; - if(!$useCache) + if(null == self::$breeze_session) { - return $session; + self::$breeze_session = $session; } - - self::$breeze_session = $session; - return self::$breeze_session; + return $session; } else { @@ -300,14 +264,11 @@ public function getBreezeSession($useCache = true) * @param String $session Session id * @return String Object id */ - public function getShortcuts($type, $session) + public function getShortcuts($type, $session = NULL) { - $url = $this->getApiUrl(array( - 'action' => 'sco-shortcuts', - 'session' => $session - )); + $url = $this->getApiUrl(array( 'action' => 'sco-shortcuts')); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); if( $xml instanceof SimpleXMLElement && 'ok' == (string)$xml->status['code'] @@ -330,15 +291,14 @@ public function getShortcuts($type, $session) * @param String $session Session id * @return String Object id */ - public function getFolderId($scoId, $session) + public function getFolderId($scoId, $session = NULL) { $url = $this->getApiUrl(array( 'action' => 'sco-info', - 'session' => $session, 'sco-id' => $scoId )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); $id = $xml->sco['folder-id']; return ($id == "" ? NULL : $id); @@ -355,11 +315,10 @@ public function getFolderId($scoId, $session) * @param String $folder_id Sco-id of the user's meetings folder * @param int $source_sco_id * @param string $ac_language - * @param String $session Session id * @return array Meeting sco-id AND Meeting url-path; NULL if something is wrong * @throws ilException */ - public function addMeeting($name, $description, $start_date, $start_time, $end_date, $end_time, $folder_id, $session, $source_sco_id = 0, $ac_language = 'de') + public function addMeeting($name, $description, $start_date, $start_time, $end_date, $end_time, $folder_id, $ac_language, $source_sco_id = 0) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -372,7 +331,6 @@ public function addMeeting($name, $description, $start_date, $start_time, $end_d 'description' => $description, 'date-begin' => $start_date . "T" . $start_time, 'date-end' => $end_date . "T" . $end_time, - 'session' => $session, 'lang' => $ac_language ); @@ -382,7 +340,8 @@ public function addMeeting($name, $description, $start_date, $start_time, $end_d } $url = $this->getApiUrl($api_parameter); - $xml = simplexml_load_file($url); + + $xml = $this->sendRequest($url); if($xml->status['code'] == "ok") { @@ -420,7 +379,7 @@ public function addMeeting($name, $description, $start_date, $start_time, $end_d * @param String $session * @return boolean Returns true if everything is ok */ - public function updateMeeting($meeting_id, $name, $description, $start_date, $start_time, $end_date, $end_time, $session, $ac_language) + public function updateMeeting($meeting_id, $name, $description, $start_date, $start_time, $end_date, $end_time, $ac_language) { global $DIC; $lng = $DIC->language(); @@ -433,10 +392,10 @@ public function updateMeeting($meeting_id, $name, $description, $start_date, $st 'description' => $description, 'date-begin' => $start_date . "T" . $start_time, 'date-end' => $end_date . "T" . $end_time, - 'session' => $session, 'lang' => $ac_language )); - $xml = simplexml_load_file($url); + + $xml = $this->sendRequest($url); if($xml->status['code'] == 'ok') { @@ -460,7 +419,7 @@ public function updateMeeting($meeting_id, $name, $description, $start_date, $st * @param String $session * @return boolean Returns true if everything is ok */ - public function deleteMeeting($sco_id, $session) + public function deleteMeeting($sco_id, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -468,11 +427,10 @@ public function deleteMeeting($sco_id, $session) $url = $this->getApiUrl(array( 'action' => 'sco-delete', - 'sco-id' => $sco_id, - 'session' => $session + 'sco-id' => $sco_id )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); //'no-data' means current sco does not exists or sco is already deleted if(($xml->status['code'] == 'ok') || ($xml->status['code'] == 'no-data')) @@ -497,7 +455,7 @@ public function deleteMeeting($sco_id, $session) * @param integer $a_meeting_id Meeting id * @return boolean Returns true if everything is ok */ - public function setMeetingPrivate($a_meeting_id, $session) + public function setMeetingPrivate($a_meeting_id, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -506,11 +464,10 @@ public function setMeetingPrivate($a_meeting_id, $session) 'action' => 'permissions-update', 'acl-id' => $a_meeting_id, 'principal-id' => 'public-access', - 'permission-id' => 'denied', - 'session' => $session + 'permission-id' => 'denied' )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); if($xml->status['code'] == "ok") { @@ -533,7 +490,7 @@ public function setMeetingPrivate($a_meeting_id, $session) * @param $a_meeting_id $meeting_id * @return boolean */ - public function setMeetingPublic($a_meeting_id, $session) + public function setMeetingPublic($a_meeting_id, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -542,11 +499,10 @@ public function setMeetingPublic($a_meeting_id, $session) 'action' => 'permissions-update', 'acl-id' => $a_meeting_id, 'principal-id' => 'public-access', - 'permission-id' => 'view-hidden', - 'session' => $session + 'permission-id' => 'view-hidden' )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); if($xml->status['code'] == "ok") { @@ -569,7 +525,7 @@ public function setMeetingPublic($a_meeting_id, $session) * @param integer $a_meeting_id * @return boolean */ - public function setMeetingProtected($a_meeting_id, $session) + public function setMeetingProtected($a_meeting_id, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -578,11 +534,10 @@ public function setMeetingProtected($a_meeting_id, $session) 'action' => 'permissions-update', 'acl-id' => $a_meeting_id, 'principal-id' => 'public-access', - 'permission-id' => 'remove', - 'session' => $session + 'permission-id' => 'remove' )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); if($xml->status['code'] == "ok") { @@ -600,7 +555,7 @@ public function setMeetingProtected($a_meeting_id, $session) } } - public function updatePermission($a_meeting_id, $session, $a_permission_id) + public function updatePermission($a_meeting_id, $session= NULL, $a_permission_id) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -609,11 +564,10 @@ public function updatePermission($a_meeting_id, $session, $a_permission_id) 'action' => 'permissions-update', 'acl-id' => $a_meeting_id, 'principal-id' => 'public-access', - 'permission-id' => $a_permission_id, - 'session' => $session + 'permission-id' => $a_permission_id )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); if($xml->status['code'] == "ok") { @@ -638,7 +592,7 @@ public function updatePermission($a_meeting_id, $session, $a_permission_id) * @param String $type Used for SWITCHaai meeting|content|... * @return String Meeting or content URL, or NULL if something is wrong */ - public function getURL($sco_id, $folder_id, $session) + public function getURL($sco_id, $folder_id, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -646,10 +600,10 @@ public function getURL($sco_id, $folder_id, $session) $url = $this->getApiUrl(array( 'action' => 'sco-contents', 'sco-id' => $folder_id, - 'filter-sco-id' => $sco_id, - 'session' => $session + 'filter-sco-id' => $sco_id )); - $xml = $this->getCachedSessionCall($url); + + $xml = $this->sendRequest($url); if($xml->status['code'] == "ok") { return (string)$xml->scos->sco->{'url-path'}; @@ -670,7 +624,7 @@ public function getURL($sco_id, $folder_id, $session) * @param String $session * @return String Meeting start date, or NULL if something is wrong */ - public function getStartDate($sco_id, $folder_id, $session) + public function getStartDate($sco_id, $folder_id, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -678,34 +632,39 @@ public function getStartDate($sco_id, $folder_id, $session) $url = $this->getApiUrl(array( 'action' => 'sco-contents', 'sco-id' => $folder_id, - 'filter-sco-id' => $sco_id, - 'session' => $session + 'filter-sco-id' => $sco_id )); - $xml = $this->getCachedSessionCall($url); + $xml = $this->sendRequest($url); - if($xml->status['code'] == "ok") + if ($xml instanceof \SimpleXMLElement) { - return (string)$xml->scos->sco->{'date-begin'}; + if($xml->status['code'] == "ok") + { + return (string)$xml->scos->sco->{'date-begin'}; + } + else + { + $ilLog->write('AdobeConnect getStartDate Response: '.$xml->asXML()); + } } else { $ilLog->write('AdobeConnect getStartDate Request: ' . $url); - $ilLog->write('AdobeConnect getStartDate Response: ' . $xml->asXML()); + $ilLog->write('The API does not respond with a valid XML body'); return NULL; } } - public function isActiveSco($session, $sco_id) + public function isActiveSco($session= NULL, $sco_id) { $url = $this->getApiUrl(array( 'action' => 'report-active-meetings', - 'filter-sco-id' => $sco_id, - 'session' => $session + 'filter-sco-id' => $sco_id )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); $counter = 0; $result = array(); if(is_array($xml->{'report-active-meetings'}->sco)) @@ -724,14 +683,13 @@ public function isActiveSco($session, $sco_id) return 0; } - public function getActiveScos($session) + public function getActiveScos($session= NULL) { $url = $this->getApiUrl(array( - 'action' => 'report-active-meetings', - 'session' => $session + 'action' => 'report-active-meetings' )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); $counter = 0; $result = array(); if($xml->{'report-active-meetings'}->sco) @@ -740,10 +698,7 @@ public function getActiveScos($session) { foreach($sco->attributes() as $name => $attr) { - //if($sco['active-participants'] >= 1) - //{ $result[$counter][(string)$name] = (string)$attr; - //} } $result[$counter]['name'] = (string)$sco->name; @@ -756,16 +711,14 @@ public function getActiveScos($session) } return 0; } - - public function getAllScos($session) + public function getAllScos($session= NULL) { $url = $this->getApiUrl(array( 'action' => 'report-bulk-objects', - 'filter-type' => 'meeting', - 'session' => $session + 'filter-type' => 'meeting' )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); $result = array(); if($xml->{'report-bulk-objects'}) @@ -794,7 +747,7 @@ public function getAllScos($session) * @param String $session * @return String Meeting start date, or NULL if something is wrong */ - public function getEndDate($sco_id, $folder_id, $session) + public function getEndDate($sco_id, $folder_id, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -802,22 +755,27 @@ public function getEndDate($sco_id, $folder_id, $session) $url = $this->getApiUrl(array( 'action' => 'sco-contents', 'sco-id' => $folder_id, - 'filter-sco-id' => $sco_id, - 'session' => $session + 'filter-sco-id' => $sco_id )); - - $xml = $this->getCachedSessionCall($url); - if($xml->status['code'] == "ok") - { - return (string)$xml->scos->sco->{'date-end'}; + $xml = $this->sendRequest($url); + if ($xml instanceof \SimpleXMLElement) + { + if($xml->status['code'] == "ok") + { + return (string)$xml->scos->sco->{'date-end'}; + } + else + { + if($xml) + { + $ilLog->write('AdobeConnect getEndDate Response: ' . $xml->asXML()); + } + } } else { - $ilLog->write('AdobeConnect getStartDate Request: ' . $url); - if($xml) - { - $ilLog->write('AdobeConnect getStartDate Response: ' . $xml->asXML()); - } + $ilLog->write('AdobeConnect getEndDate Request: '.$url); + $ilLog->write('The API does not respond with a valid XML body'); return NULL; } } @@ -829,7 +787,7 @@ public function getEndDate($sco_id, $folder_id, $session) * @param String $session * @return String Meeting or content name, or NULL if something is wrong */ - public function getName($sco_id, $folder_id, $session) + public function getName($sco_id, $folder_id, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -837,23 +795,28 @@ public function getName($sco_id, $folder_id, $session) $url = $this->getApiUrl(array( 'action' => 'sco-contents', 'sco-id' => $folder_id, - 'filter-sco-id' => $sco_id, - 'session' => $session + 'filter-sco-id' => $sco_id )); - $xml = $this->getCachedSessionCall($url); - - if($xml->status['code'] == "ok") + $xml = $this->sendRequest($url); + if ($xml instanceof \SimpleXMLElement) { - return (string)$xml->scos->sco->{'name'}; + if($xml->status['code'] == "ok") + { + return (string)$xml->scos->sco->{'name'}; + } + else + { + if($xml) + { + $ilLog->write('AdobeConnect getName Response: ' . $xml->asXML()); + } + } } else { $ilLog->write('AdobeConnect getName Request: ' . $url); - if($xml) - { - $ilLog->write('AdobeConnect getName Response: ' . $xml->asXML()); - } + $ilLog->write('The API does not respond with a valid XML body'); return NULL; } } @@ -865,7 +828,7 @@ public function getName($sco_id, $folder_id, $session) * @param String $session * @return String Meeting or content description, or NULL if something is wrong */ - public function getDescription($sco_id, $folder_id, $session) + public function getDescription($sco_id, $folder_id, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -873,23 +836,27 @@ public function getDescription($sco_id, $folder_id, $session) $url = $this->getApiUrl(array( 'action' => 'sco-contents', 'sco-id' => $folder_id, - 'filter-sco-id' => $sco_id, - 'session' => $session + 'filter-sco-id' => $sco_id )); - $xml = $this->getCachedSessionCall($url); - - if($xml->status['code'] == "ok") + $xml = $this->sendRequest($url); + if ($xml instanceof \SimpleXMLElement) { - return (string)$xml->scos->sco->{'description'}; + if($xml->status['code'] == "ok") + { + return (string)$xml->scos->sco->{'description'}; + } + else + { + if($xml) + { + $ilLog->write('AdobeConnect getDescription Response: ' . $xml->asXML()); + } + } } else { $ilLog->write('AdobeConnect getDescription Request: ' . $url); - if($xml) - { - $ilLog->write('AdobeConnect getDescription Response: ' . $xml->asXML()); - } return NULL; } } @@ -901,7 +868,7 @@ public function getDescription($sco_id, $folder_id, $session) * @param String $session * @return String Meeting or content creation date, or NULL if something is wrong */ - public function getDateCreated($sco_id, $folder_id, $session) + public function getDateCreated($sco_id, $folder_id, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -909,23 +876,27 @@ public function getDateCreated($sco_id, $folder_id, $session) $url = $this->getApiUrl(array( 'action' => 'sco-contents', 'sco-id' => $folder_id, - 'filter-sco-id' => $sco_id, - 'session' => $session + 'filter-sco-id' => $sco_id )); - $xml = $this->getCachedSessionCall($url); - - if($xml->status['code'] == "ok") + $xml = $this->sendRequest($url); + if ($xml instanceof \SimpleXMLElement) { - return (string)$xml->scos->sco->{'date-created'}; + if($xml->status['code'] == "ok") + { + return (string)$xml->scos->sco->{'date-created'}; + } + else + { + if($xml) + { + $ilLog->write('AdobeConnect getDateCreated Response: ' . $xml->asXML()); + } + } } else { $ilLog->write('AdobeConnect getDateCreated Request: ' . $url); - if($xml) - { - $ilLog->write('AdobeConnect getDateCreated Response: ' . $xml->asXML()); - } return NULL; } } @@ -937,7 +908,7 @@ public function getDateCreated($sco_id, $folder_id, $session) * @param String $session * @return String Meeting or content modification date, or NULL if something is wrong */ - public function getDateModified($sco_id, $folder_id, $session) + public function getDateModified($sco_id, $folder_id, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -945,23 +916,27 @@ public function getDateModified($sco_id, $folder_id, $session) $url = $this->getApiUrl(array( 'action' => 'sco-contents', 'sco-id' => $folder_id, - 'filter-sco-id' => $sco_id, - 'session' => $session + 'filter-sco-id' => $sco_id )); - $xml = $this->getCachedSessionCall($url); - - if($xml->status['code'] == "ok") + $xml = $this->sendRequest($url); + if ($xml instanceof \SimpleXMLElement) { - return (string)$xml->scos->sco->{'date-modified'}; + if($xml->status['code'] == "ok") + { + return (string)$xml->scos->sco->{'date-modified'}; + } + else + { + if($xml) + { + $ilLog->write('AdobeConnect getDateModified Response: ' . $xml->asXML()); + } + } } else { $ilLog->write('AdobeConnect getDateModified Request: ' . $url); - if($xml) - { - $ilLog->write('AdobeConnect getDateModified Response: ' . $xml->asXML()); - } return NULL; } } @@ -973,7 +948,7 @@ public function getDateModified($sco_id, $folder_id, $session) * @param String $session * @return String Content duration, or NULL if something is wrong */ - public function getDuration($sco_id, $folder_id, $session) + public function getDuration($sco_id, $folder_id, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -981,23 +956,27 @@ public function getDuration($sco_id, $folder_id, $session) $url = $this->getApiUrl(array( 'action' => 'sco-contents', 'sco-id' => $folder_id, - 'filter-sco-id' => $sco_id, - 'session' => $session + 'filter-sco-id' => $sco_id )); - $xml = $this->getCachedSessionCall($url); - - if($xml->status['code'] == "ok") + $xml = $this->sendRequest($url); + if ($xml instanceof \SimpleXMLElement) { - return (string)$xml->scos->sco->duration; + if($xml->status['code'] == "ok") + { + return (string)$xml->scos->sco->duration; + } + else + { + if($xml) + { + $ilLog->write('AdobeConnect getDuration Response: ' . $xml->asXML()); + } + } } else { $ilLog->write('AdobeConnect getDuration Request: ' . $url); - if($xml) - { - $ilLog->write('AdobeConnect getDuration Response: ' . $xml->asXML()); - } return NULL; } } @@ -1008,52 +987,56 @@ public function getDuration($sco_id, $folder_id, $session) * @param String $session * @return array */ - public function getContentIds($meeting_id, $session) + public function getContentIds($meeting_id, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); $url = $this->getApiUrl(array( 'action' => 'sco-contents', - 'sco-id' => $meeting_id, - 'session' => $session + 'sco-id' => $meeting_id )); - $xml = $this->getCachedSessionCall($url); - - if($xml->status['code'] == "ok") + $xml = $this->sendRequest($url); + if ($xml instanceof \SimpleXMLElement) { - $ids = array(); - $i = 0; - $contents = array(); - $records = array(); - - foreach($xml->scos->sco as $sco) + if($xml->status['code'] == "ok") { - if($sco['source-sco-id'] == "" - && $sco['duration'] == "" - ) + $ids = array(); + $i = 0; + $contents = array(); + $records = array(); + + foreach($xml->scos->sco as $sco) { - $contents[$i] = (string)$sco['sco-id']; - $i++; + if($sco['source-sco-id'] == "" + && $sco['duration'] == "" + ) + { + $contents[$i] = (string)$sco['sco-id']; + $i++; + } + else if($sco['source-sco-id'] == "" + && $sco['duration'] != "" + ) + { + $records[$i] = (string)$sco['sco-id']; + $i++; + } } - else if($sco['source-sco-id'] == "" - && $sco['duration'] != "" - ) + return array_merge($contents, $records); + } + else + { + if($xml) { - $records[$i] = (string)$sco['sco-id']; - $i++; + $ilLog->write('AdobeConnect getDuration Response: ' . $xml->asXML()); } } - return array_merge($contents, $records); } else { $ilLog->write('AdobeConnect getDuration Request: ' . $url); - if($xml) - { - $ilLog->write('AdobeConnect getDuration Response: ' . $xml->asXML()); - } return NULL; } } @@ -1064,28 +1047,30 @@ public function getContentIds($meeting_id, $session) * @param String $session * @return array */ - public function getRecordIds($meeting_id, $session) + public function getRecordIds($meeting_id, $session= NULL) { $url = $this->getApiUrl(array( 'action' => 'sco-contents', - 'sco-id' => $meeting_id, - 'session' => $session + 'sco-id' => $meeting_id )); - $xml = $this->getCachedSessionCall($url); - if($xml->status['code'] == "ok") + $xml = $this->sendRequest($url); + if ($xml instanceof \SimpleXMLElement) { - $ids = array(); - $i = 0; - foreach($xml->scos->sco as $sco) + if($xml->status['code'] == "ok") { - if($sco['source-sco-id'] == "" && $sco['duration'] != "") + $ids = array(); + $i = 0; + foreach($xml->scos->sco as $sco) { - $ids[$i] = (string)$sco['sco-id']; - $i++; + if($sco['source-sco-id'] == "" && $sco['duration'] != "") + { + $ids[$i] = (string)$sco['sco-id']; + $i++; + } } + return $ids; } - return $ids; } return NULL; } @@ -1099,7 +1084,7 @@ public function getRecordIds($meeting_id, $session) * @return String * @throws ilAdobeConnectDuplicateContentException */ - public function addContent($folder_id, $title, $description, $session) + public function addContent($folder_id, $title, $description, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -1108,11 +1093,10 @@ public function addContent($folder_id, $title, $description, $session) 'action' => 'sco-update', 'name' => $title, 'folder-id' => $folder_id, - 'description' => $description, - 'session' => $session + 'description' => $description )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); if($xml instanceof SimpleXMLElement && $xml->status['code'] == 'ok') { $server = $this->server; @@ -1120,7 +1104,7 @@ public function addContent($folder_id, $title, $description, $session) { $server = substr($server, 0, -1); } - return $server . "/api/xml?action=sco-upload&sco-id=" . (string)$xml->sco['sco-id'] . "&session=" . $session; + return $server . "/api/xml?action=sco-upload&sco-id=".(string)$xml->sco['sco-id']; } else { @@ -1150,7 +1134,7 @@ public function addContent($folder_id, $title, $description, $session) * @return boolean Returns true if everything is ok * @throws ilAdobeConnectDuplicateContentException */ - public function updateContent($sco_id, $title, $description, $session) + public function updateContent($sco_id, $title, $description, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -1159,10 +1143,10 @@ public function updateContent($sco_id, $title, $description, $session) 'action' => 'sco-update', 'name' => $title, 'sco-id' => $sco_id, - 'description' => $description, - 'session' => $session + 'description' => $description )); - $xml = simplexml_load_file($url); + + $xml = $this->sendRequest($url); if($xml instanceof SimpleXMLElement && $xml->status['code'] == 'ok') { @@ -1193,25 +1177,27 @@ public function updateContent($sco_id, $title, $description, $session) * @param String $session * @return boolean Returns true if everything is ok */ - public function deleteContent($sco_id, $session) + public function deleteContent($sco_id, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); $url = $this->getApiUrl(array( 'action' => 'sco-delete', - 'sco-id' => $sco_id, - 'session' => $session + 'sco-id' => $sco_id )); $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); + + if($xml instanceof SimpleXMLElement) + { if($xml->status['code'] == "ok") { return true; } else { - $ilLog->write('AdobeConnect deleteContent Request: ' . $url); if($xml) { $ilLog->write('AdobeConnect deleteContent Response: ' . $xml->asXML()); @@ -1219,19 +1205,24 @@ public function deleteContent($sco_id, $session) return false; } } - + else + { + $ilLog->write('AdobeConnect deleteContent Request: ' . $url); + return false; + } + } + /** * Upload a content on the Adobe Connect server * @param $sco_id * @param String $session * @return String */ - public function uploadContent($sco_id, $session) + public function uploadContent($sco_id, $session= NULL) { $url = $this->getApiUrl(array( 'action' => 'sco-upload', - 'sco-id' => $sco_id, - 'session' => $session + 'sco-id' => $sco_id )); return $url; @@ -1242,17 +1233,17 @@ public function uploadContent($sco_id, $session) * @param string $session * @return bool|string */ - public function searchUser($login, $session) + public function searchUser($login, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); $url = $this->getApiUrl(array( 'action' => 'principal-list', - 'filter-login' => $login, - 'session' => $session + 'filter-login' => $login )); - $xml = simplexml_load_file($url); + + $xml = $this->sendRequest($url); if($xml instanceof \SimpleXMLElement && $xml->status['code'] == 'ok') { @@ -1285,7 +1276,7 @@ public function searchUser($login, $session) * @param String $session * @return boolean Returns true if everything is ok */ - public function addUser($login, $email, $pass, $first_name, $last_name, $session) + public function addUser($login, $email, $pass, $first_name, $last_name, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -1298,25 +1289,29 @@ public function addUser($login, $email, $pass, $first_name, $last_name, $session 'first-name' => $first_name, 'last-name' => $last_name, 'type' => 'user', - 'has-children' => 0, - 'session' => $session + 'has-children' => 0 )); $ilLog->write("addUser Url: " . $url); - $xml = simplexml_load_file($url); - - if($xml->status['code'] == 'ok') + $xml = $this->sendRequest($url); + if($xml instanceof SimpleXMLElement) { - return true; + if($xml->status['code'] == 'ok') + { + return true; + } + else + { + if($xml) + { + $ilLog->write('AdobeConnect addUser Response: ' . $xml->asXML()); + } + return false; + } } else { $ilLog->write('AdobeConnect addUser Request: ' . $url); - if($xml) - { - $ilLog->write('AdobeConnect addUser Response: ' . $xml->asXML()); - } - return false; } } @@ -1327,7 +1322,7 @@ public function addUser($login, $email, $pass, $first_name, $last_name, $session * @param String $session * @return array */ - public function getMeetingsParticipants($meeting_id, $session) + public function getMeetingsParticipants($meeting_id, $session= NULL) { $result = array(); @@ -1336,26 +1331,22 @@ public function getMeetingsParticipants($meeting_id, $session) $host = $this->getApiUrl(array( 'action' => 'permissions-info', 'acl-id' => $meeting_id, - 'session' => $session, 'filter-permission-id' => 'host' )); $mini_host = $this->getApiUrl(array( 'action' => 'permissions-info', 'acl-id' => $meeting_id, - 'session' => $session, 'filter-permission-id' => 'mini-host' )); $view = $this->getApiUrl(array( 'action' => 'permissions-info', 'acl-id' => $meeting_id, - 'session' => $session, 'filter-permission-id' => 'view' )); $denied = $this->getApiUrl(array( 'action' => 'permissions-info', 'acl-id' => $meeting_id, - 'session' => $session, 'filter-permission-id' => 'denied' )); } @@ -1364,80 +1355,91 @@ public function getMeetingsParticipants($meeting_id, $session) $host = $this->getApiUrl(array( 'action' => 'permissions-info', 'acl-id' => $meeting_id, - 'session' => $session, 'filter-type' => 'user', 'filter-permission-id' => 'host' )); $mini_host = $this->getApiUrl(array( 'action' => 'permissions-info', 'acl-id' => $meeting_id, - 'session' => $session, 'filter-type' => 'user', 'filter-permission-id' => 'mini-host' )); $view = $this->getApiUrl(array( 'action' => 'permissions-info', 'acl-id' => $meeting_id, - 'session' => $session, 'filter-type' => 'user', 'filter-permission-id' => 'view' )); $denied = $this->getApiUrl(array( 'action' => 'permissions-info', 'acl-id' => $meeting_id, - 'session' => $session, 'filter-type' => 'user', 'filter-permission-id' => 'denied' )); } - $xml_host = simplexml_load_file($host); - foreach($xml_host->permissions->principal as $user) + $result = array(); + + $xml_host = $this->sendRequest($host); + + if(is_array($xml_host->permissions->principal)) { - $result[(string)$user->login] = array("name" => (string)$user->name, "login" => (string)$user->login, 'status' => 'host'); + foreach($xml_host->permissions->principal as $user) + { + $result[(string)$user->login] = array("name" => (string)$user->name, "login" => (string)$user->login, 'status' => 'host'); + } } - - $xml_mini_host = simplexml_load_file($mini_host); - foreach($xml_mini_host->permissions->principal as $user) + + $xml_mini_host = $this->sendRequest($mini_host); + if(is_array($xml_mini_host->permissions->principal)) { - $result[(string)$user->login] = array("name" => (string)$user->name, "login" => (string)$user->login, 'status' => 'mini-host'); + foreach($xml_mini_host->permissions->principal as $user) + { + $result[(string)$user->login] = array("name" => (string)$user->name, "login" => (string)$user->login, 'status' => 'mini-host'); + } } - - $xml_view = simplexml_load_file($view); - foreach($xml_view->permissions->principal as $user) + + $xml_view = $this->sendRequest($view); + if(is_array($xml_view->permissions->principal)) { - $result[(string)$user->login] = array("name" => (string)$user->name, "login" => (string)$user->login, 'status' => 'view'); + foreach($xml_view->permissions->principal as $user) + { + $result[(string)$user->login] = array("name" => (string)$user->name, "login" => (string)$user->login, 'status' => 'view'); + } } - - $xml_denied = simplexml_load_file($denied); - foreach($xml_denied->permissions->principal as $user) + + $xml_denied = $this->sendRequest($denied); + if(is_array($xml_denied->permissions->principal)) { - $result[(string)$user->login] = array("name" => (string)$user->name, "login" => (string)$user->login, 'status' => 'denied'); + foreach($xml_denied->permissions->principal as $user) + { + $result[(string)$user->login] = array("name" => (string)$user->name, "login" => (string)$user->login, 'status' => 'denied'); + } } - + return is_array($result) ? $result : array(); } /** * Add a host to the meeting + * * @param String $meeting_id * @param String $login * @param String $session * @return boolean Returns true if everything is ok */ - public function addMeetingParticipant($meeting_id, $login, $session) + public function addMeetingParticipant($meeting_id, $login, $session= NULL) { $principal_id = $this->getPrincipalId($login, $session); $url = $this->getApiUrl(array( 'action' => 'permissions-update', 'acl-id' => $meeting_id, - 'session' => $session, 'principal-id' => $principal_id, 'permission-id' => 'view' )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); return ($xml->status['code'] == "ok" ? true : false); } @@ -1449,18 +1451,18 @@ public function addMeetingParticipant($meeting_id, $login, $session) * @param String $session * @return boolean Returns true if everything is ok */ - public function addMeetingHost($meeting_id, $login, $session) + public function addMeetingHost($meeting_id, $login, $session= NULL) { $principal_id = $this->getPrincipalId($login, $session); $url = $this->getApiUrl(array( 'action' => 'permissions-update', 'acl-id' => $meeting_id, - 'session' => $session, 'principal-id' => $principal_id, 'permission-id' => 'host' )); - $xml = simplexml_load_file($url); + + $xml = $this->sendRequest($url); return ($xml->status['code'] == "ok" ? true : false); } @@ -1472,30 +1474,23 @@ public function addMeetingHost($meeting_id, $login, $session) * @param String $session * @return boolean Returns true if everything is ok */ - public function addMeetingModerator($meeting_id, $login, $session) + public function addMeetingModerator($meeting_id, $login, $session= NULL) { $principal_id = $this->getPrincipalId($login, $session); $url = $this->getApiUrl(array( 'action' => 'permissions-update', 'acl-id' => $meeting_id, - 'session' => $session, 'principal-id' => $principal_id, 'permission-id' => 'mini-host' )); - $xml = simplexml_load_file($url); + + $xml = $this->sendRequest($url); return ($xml->status['code'] == "ok" ? true : false); } - /** - * @param $meeting_id - * @param $login - * @param $session - * @param $permission - * @return bool - */ - public function updateMeetingParticipant($meeting_id, $login, $session, $permission) + public function updateMeetingParticipant($meeting_id, $login, $session= NULL, $permission) { $principal_id = $this->getPrincipalId($login, $session); @@ -1503,27 +1498,14 @@ public function updateMeetingParticipant($meeting_id, $login, $session, $permiss 'action' => 'permissions-update', 'principal-id' => $principal_id, 'acl-id' => $meeting_id, - 'session' => $session, 'permission-id' => $permission )); - $ctx = $this->proxy(array()); - $result = file_get_contents($url, false, $ctx); - $xml = simplexml_load_string($result); + $xml = $this->sendRequest($url); if($xml->status['code'] == 'ok') { return true; } - // //deactivated for switch to avoid failure-message - // else - // { - // $ilLog->write('AdobeConnect updateMeetingParticipant Request: '.$url); - // $ilLog->write('AdobeConnect updateMeetingParticipant Response: '.$xml->asXML()); - // - // # ilUtil::sendFailure($lng->txt('add_user_failed')); - // - // return false; - // } } /** @@ -1533,18 +1515,17 @@ public function updateMeetingParticipant($meeting_id, $login, $session, $permiss * @param String $session * @return boolean Returns true if everything is ok */ - public function deleteMeetingParticipant($meeting_id, $login, $session) + public function deleteMeetingParticipant($meeting_id, $login, $session= NULL) { $principal_id = $this->getPrincipalId($login, $session); $url = $this->getApiUrl(array( 'action' => 'permissions-update', 'acl-id' => $meeting_id, - 'session' => $session, 'principal-id' => $principal_id, 'permission-id' => 'remove' )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); return ($xml->status['code'] == "ok" ? true : false); } @@ -1554,14 +1535,13 @@ public function deleteMeetingParticipant($meeting_id, $login, $session) * @param String $session * @return array */ - public function getMeetingsId($session) + public function getMeetingsId($session= NULL) { $url = $this->getApiUrl(array( 'action' => 'report-bulk-objects', - 'filter-type' => 'meeting', - 'session' => $session + 'filter-type' => 'meeting' )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); foreach($xml->{'report-bulk-objects'}->row as $meeting) { @@ -1580,30 +1560,34 @@ public function getMeetingsId($session) * @param String $session * @return String */ - public function getPrincipalId($login, $session) + public function getPrincipalId($login, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); $url = $this->getApiUrl(array( 'action' => 'principal-list', - 'filter-login' => $login, - 'session' => $session + 'filter-login' => $login )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); - if($xml->status['code'] == "ok") + if($xml instanceof SimpleXMLElement) { - return (string)$xml->{'principal-list'}->principal['principal-id']; + if($xml->status['code'] == "ok") + { + return (string)$xml->{'principal-list'}->principal['principal-id']; + } + else + { + if($xml) + { + $ilLog->write('AdobeConnect getPrincipalId Response: ' . $xml->asXML()); + } + } } else - { - $ilLog->write('AdobeConnect getPrincipalId Request: ' . $url); - if($xml) { - $ilLog->write('AdobeConnect getPrincipalId Response: ' . $xml->asXML()); - } - + $ilLog->write('AdobeConnect getPrincipalId Request: ' . $url); return NULL; } } @@ -1615,17 +1599,16 @@ public function getPrincipalId($login, $session) * @param String $session * @return boolean */ - public function isParticipant($login, $meeting, $session) + public function isParticipant($login, $meeting, $session= NULL) { $p_id = $this->getPrincipalId($login, $session); $url = $this->getApiUrl(array( 'action' => 'permissions-info', 'acl-id' => $meeting, - 'filter-principal-id' => $p_id, - 'session' => $session + 'filter-principal-id' => $p_id )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); if(in_array((string)$xml->permissions->principal['permission-id'], array('host', 'mini-host', 'view'))) { @@ -1637,21 +1620,14 @@ public function isParticipant($login, $meeting, $session) } } - /** - * @param $meeting - * @param $session - * @return string - */ - public function getPermissionId($meeting, $session) + public function getPermissionId($meeting, $session= NULL) { - $url2 = $this->getApiUrl(array( + $url = $this->getApiUrl(array( 'action' => 'permissions-info', 'acl-id' => $meeting, - 'principal-id' => 'public-access', - 'session' => $session + 'principal-id' => 'public-access' )); - - $xml2 = simplexml_load_file($url2); + $xml2 = $this->sendRequest($url); $permission_id = (string)$xml2->permission['permission-id']; // ADOBE CONNECT API BUG!! if access-level is "PROTECTED" the api does not return a proper permission_id. it returns an empty string @@ -1665,12 +1641,7 @@ public function getPermissionId($meeting, $session) } } - /** - * @param $a_sco_id - * @param $session - * @return array - */ - public function getActiveUsers($a_sco_id, $session) + public function getActiveUsers($a_sco_id, $session= NULL) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -1678,34 +1649,37 @@ public function getActiveUsers($a_sco_id, $session) $url = $this->getApiUrl(array( 'action' => 'report-bulk-consolidated-transactions', 'filter-type' => 'meeting', - 'session' => $session, 'filter-sco-id' => $a_sco_id )); - - $xml = simplexml_load_file($url); - - if($xml->status['code'] == "ok") + $xml = $this->sendRequest($url); + + if($xml instanceof SimpleXMLElement) { - foreach($xml->{'report-bulk-consolidated-transactions'}->row as $meeting) + if($xml->status['code'] == "ok") { - if($meeting->{'status'} == 'in-progress') + foreach($xml->{'report-bulk-consolidated-transactions'}->row as $meeting) { - $result[] = (string)$meeting->{'user-name'}; + if($meeting->{'status'} == 'in-progress') + { + $result[] = (string)$meeting->{'user-name'}; + } + } + return $result; + } + else + { + if($xml) + { + $ilLog->write('AdobeConnect getActiveUsers Response: ' . $xml->asXML()); } } - return $result; } else { $ilLog->write('AdobeConnect getActiveUsers Request: ' . $url); - if($xml) - { - $ilLog->write('AdobeConnect getActiveUsers Response: ' . $xml->asXML()); - } - + } return array(); } - } /** * Generates an url encoded string for api calls @@ -1736,6 +1710,7 @@ protected function getApiUrl($params) } /** + * @deprecated * Performs a cached call based on a static cache. * @param string $url * @return SimpleXMLElement @@ -1745,11 +1720,10 @@ protected function getCachedSessionCall($url) $hash = $url; if(isset(self::$scocontent_cache[$hash])) { - return self::$scocontent_cache[$hash]; +// return self::$scocontent_cache[$hash]; } - $xml = simplexml_load_file($url); - + $xml = $this->sendRequest($url); self::$scocontent_cache[$hash] = $xml; return $xml; @@ -1775,27 +1749,13 @@ private function useHTTPHeaderAuthentification($user) $x_user_id . ': ' . $user ) ); - - $opts = array( - 'http' => array( - 'method' => 'GET', - 'header' => $headers - ), - 'https' => array( - 'method' => 'GET', - 'header' => $headers - ), - ); - + $url = $this->getApiUrl(array( 'action' => 'login', 'external-auth' => 'use' )); - $ctx = $this->proxy($opts); - $result = file_get_contents($url, false, $ctx); - - $xml = simplexml_load_string($result); + $xml = $this->sendRequest($url); if($xml instanceof SimpleXMLElement && $xml->status['code'] == 'ok') { foreach($http_response_header as $header) @@ -1814,11 +1774,7 @@ private function useHTTPHeaderAuthentification($user) } return false; } - - /** - * @param $user - * @return null|string - */ + private function usePasswordAuthentication($user) { global $DIC; @@ -1841,11 +1797,10 @@ private function usePasswordAuthentication($user) } } - $session = $this->getBreezeSession(false); - if($this->login($user, $pwd, $session)) + if($this->login($user, $pwd, null)) { $ilLog->write("Adobe Connect " . __METHOD__ . ": Successfully authenticated session (Id: " . $ilUser->getId() . " | " . $ilUser->getLogin() . ")."); - return $session; + return true; } else { @@ -1855,10 +1810,10 @@ private function usePasswordAuthentication($user) $ilUser->setPref('xavc_pwd', $pwd); $ilUser->writePrefs(); - if($this->login($user, $pwd, $session)) + if($this->login($user, $pwd, null)) { $ilLog->write("Adobe Connect " . __METHOD__ . ": Successfully authenticated session (Id: " . $ilUser->getId() . " | " . $ilUser->getLogin() . ")."); - return $session; + return true; } else { @@ -1880,7 +1835,7 @@ private function usePasswordAuthentication($user) * @param String $session * @return String Meeting or content modification date, or NULL if something is wrong */ - public function getDateEnd($sco_id, $folder_id, $session) + public function getDateEnd($sco_id, $folder_id, $session = null) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -1888,45 +1843,42 @@ public function getDateEnd($sco_id, $folder_id, $session) $url = $this->getApiUrl(array( 'action' => 'sco-contents', 'sco-id' => $folder_id, - 'filter-sco-id' => $sco_id, - 'session' => $session + 'filter-sco-id' => $sco_id )); - $xml = $this->getCachedSessionCall($url); - - if($xml->status['code'] == "ok") + $xml = $this->sendRequest($url); + if($xml instanceof SimpleXMLElement) { - return (string)$xml->scos->sco->{'date-end'}; + if($xml->status['code'] == "ok") + { + return (string)$xml->scos->sco->{'date-end'}; + } + else + { + if($xml) + { + $ilLog->write('AdobeConnect getDateEnd Response: ' . $xml->asXML()); + } + } } else { $ilLog->write('AdobeConnect getDateEnd Request: ' . $url); - if($xml) - { - $ilLog->write('AdobeConnect getDateEnd Response: ' . $xml->asXML()); - } - + } return NULL; } - } - /** - * @param $login - * @param $session - * @return null|string - */ - public function lookupUserFolderId($login, $session) + public function lookupUserFolderId($login, $session = null) { $umf_id = $this->getShortcuts('user-meetings', $session); $url = $this->getApiUrl(array( 'action' => 'sco-contents', 'sco-id' => $umf_id, - 'filter-name' => $login, - 'session' => $session + 'filter-name' => $login )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); $id = NULL; if( @@ -1946,58 +1898,51 @@ public function lookupUserFolderId($login, $session) return $id; } - /** - * @param $login - * @param $session - * @return null|string - */ - public function createUserFolder($login, $session) + public function createUserFolder($login, $session = null) { - global $DIC; - $ilLog = $DIC->logger()->root(); - + global $ilLog; $umf_id = $this->getShortcuts('user-meetings', $session); $url = $this->getApiUrl(array( 'action' => 'sco-update', 'folder-id' => $umf_id, 'type' => 'folder', - 'name' => $login, - 'session' => $session + 'name' => $login )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); $id = NULL; - - if($xml->status['code'] == "ok") + if($xml instanceof SimpleXMLElement) { - return (string)$xml->sco['sco-id']; + if($xml->status['code'] == "ok") + { + return (string)$xml->sco['sco-id']; + } + else + { + if($xml) + { + $ilLog->write('AdobeConnect createUserFolder Response: ' . $xml->asXML()); + } + } } else { $ilLog->write('AdobeConnect createUserFolder Request: ' . $url); - if($xml) - { - $ilLog->write('AdobeConnect createUserFolder Response: ' . $xml->asXML()); - } } + return NULL; } - /** - * @param $folder_id - * @param $session - * @return array - */ - public function getScosByFolderId($folder_id, $session) + + public function getScosByFolderId($folder_id , $session = null) { $url = $this->getApiUrl(array( 'action' => 'sco-contents', - 'sco-id' => $folder_id, - 'session' => $session + 'sco-id' => $folder_id )); - $xml = simplexml_load_file($url); + $xml = $this->sendRequest($url); $result = array(); if($xml instanceof SimpleXMLElement && 'ok' == (string)$xml->status['code']) @@ -2006,6 +1951,8 @@ public function getScosByFolderId($folder_id, $session) { if($meeting['type'] == 'meeting') { + $id = (string)$meeting['sco-id']; + $result[(string)$meeting['sco-id']]['sco_id'] = (string)$meeting['sco-id']; $result[(string)$meeting['sco-id']]['sco_name'] = (string)$meeting->{'name'}; $result[(string)$meeting['sco-id']]['description'] = (string)$meeting->{'description'}; @@ -2017,13 +1964,7 @@ public function getScosByFolderId($folder_id, $session) return $result; } - /** - * @param $sco_id - * @param $folder_id - * @param $session - * @return array|null - */ - public function getScoData($sco_id, $folder_id, $session) + public function getScoData($sco_id, $folder_id, $session = null) { global $DIC; $ilLog = $DIC->logger()->root(); @@ -2031,58 +1972,63 @@ public function getScoData($sco_id, $folder_id, $session) $url = $this->getApiUrl(array( 'action' => 'sco-contents', 'sco-id' => $folder_id, - 'filter-sco-id' => $sco_id, - 'session' => $session + 'filter-sco-id' => $sco_id )); - $xml = $this->getCachedSessionCall($url); + $xml = $this->sendRequest($url); $data = array(); - if($xml->status['code'] == "ok") + if($xml instanceof SimpleXMLElement) { - $data['start_date'] = (string)$xml->scos->sco->{'date-begin'}; - $data['end_date'] = (string)$xml->scos->sco->{'date-end'}; + if($xml->status['code'] == "ok") + { + $data['start_date'] = (string)$xml->scos->sco->{'date-begin'}; + $data['end_date'] = (string)$xml->scos->sco->{'date-end'}; + return $data; + } + else + { + if($xml) + { + $ilLog->write('AdobeConnect getStartDate Response: ' . $xml->asXML()); + } + } } else { $ilLog->write('AdobeConnect getStartDate Request: ' . $url); - if($xml) - { - $ilLog->write('AdobeConnect getStartDate Response: ' . $xml->asXML()); - } - + } return NULL; } - - return $data; - } - + /** * lookup content-attribute 'icon' * if icon == 'archive' the content is a record */ - public function getContentIconAttribute($sco_id, $folder_id, $session) + public function getContentIconAttribute($sco_id, $folder_id, $session = null) { $url = $this->getApiUrl(array( 'action' => 'sco-contents', 'sco-id' => $folder_id, - 'filter-sco-id' => $sco_id, - 'session' => $session + 'filter-sco-id' => $sco_id )); - $xml = $this->getCachedSessionCall($url); + $xml = $this->sendRequest($url); $icon = ''; - - if($xml->status['code'] == "ok") + if($xml instanceof SimpleXMLElement) { - foreach($xml->scos->sco as $sco) + if($xml->status['code'] == "ok") { - $icon = (string)$sco['icon']; + foreach($xml->scos->sco as $sco) + { + $icon = (string)$sco['icon']; + } } + return $icon; } - return $icon; + return null; } - + /** * @param $pluginObj * @return array @@ -2092,15 +2038,11 @@ public function getTemplates($pluginObj) $txt_shared_meeting_templates = $pluginObj->txt('shared_meeting_templates'); $txt_my_meeting_templates = $pluginObj->txt('my_meeting_templates'); - $session = $this->getAdminSession(); - $url_1 = $this->getApiUrl(array( - 'action' => 'sco-shortcuts', - 'session' => $session - )); + $url_1 = $this->getApiUrl(array('action' => 'sco-shortcuts')); - $xml = simplexml_load_file($url_1); + $xml = $this->sendRequest($url_1); $templates = array(); - if(is_array($xml->shortcuts->sco)) + if($xml instanceof SimpleXMLElement) { foreach($xml->shortcuts->sco as $folder) { @@ -2108,15 +2050,10 @@ public function getTemplates($pluginObj) { $sco_id = (string)$folder['sco-id']; $txt_folder_name = $folder['type'] == 'shared-meeting-templates' ? $txt_shared_meeting_templates : $txt_my_meeting_templates; - $url_2 = $this->getApiUrl(array( - 'action' => 'sco-contents', - 'sco-id' => $sco_id, - 'session' => $session - - )); - $xml_2 = simplexml_load_file($url_2); - if(is_array($xml_2->scos->sco)) + $url_2 = $this->getApiUrl(array('action' => 'sco-contents', 'sco-id' => $sco_id)); + $xml_2 = $this->sendRequest($url_2); + if($xml_2 instanceof SimpleXMLElement) { foreach($xml_2->scos->sco as $sco) { @@ -2176,5 +2113,48 @@ protected function proxy($ctx = null) } return null; + } + + public function appendSessionCookie($url) + { + return $url.'&session='.self::$breeze_session; + } + + /** + * @param string $url + * @return string The XML payload + */ + public function sendRequest($url) + { + global $DIC; + + if($this->enhanced_security_mode == false || $this->adcInfo->getAuthMode() == ilAdobeConnectServer::AUTH_MODE_DFN) + { + $url = $this->appendSessionCookie($url); + } + + $session = 'ac_sess_' . $DIC->user()->getId() ; + $apiCookiePath = self::XAVC_COOKIE_PATH. $session . '.txt'; + + $request = \Httpful\Request::get($url, null, 'text/xml') + ->expectsType('xml') + ->addOnCurlOption(CURLOPT_COOKIEJAR, $apiCookiePath) + ->addOnCurlOption(CURLOPT_COOKIEFILE, $apiCookiePath) + ->withoutStrictSsl(); + + require_once('Services/Http/classes/class.ilProxySettings.php'); + if (ilProxySettings::_getInstance()->isActive()) + { + $proxyHost = ilProxySettings::_getInstance()->getHost(); + $proxyPort = ilProxySettings::_getInstance()->getPort(); + + $request = $request->useProxy($proxyHost, $proxyPort); + } + + $response = $request->send(); + + return $response->body; + } + } diff --git a/classes/class.ilObjAdobeConnect.php b/classes/class.ilObjAdobeConnect.php index 4fd93e5..26fb95d 100755 --- a/classes/class.ilObjAdobeConnect.php +++ b/classes/class.ilObjAdobeConnect.php @@ -53,6 +53,7 @@ class ilObjAdobeConnect extends ilObjectPlugin /** * @var null + * */ private $contact_info = NULL; @@ -139,7 +140,7 @@ class ilObjAdobeConnect extends ilObjectPlugin * @var bool */ public $use_meeting_template = false; - + public $session_instance; /** * @var string */ @@ -436,20 +437,7 @@ public function useExistingVC($obj_id, $sco_id) global $DIC; $ilUser = $DIC->user(); $ilDB = $DIC->database(); - - // receive breeze session - $session = $this->xmlApi->getBreezeSession(); - if(!$session) - { - throw new ilException('xavc_connection_error'); - } - - // access check - if(!$this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { - throw new ilException('xavc_authentication_error'); - } - + // receive folder id $this->externalLogin = $this->checkExternalUser(); $folder_id = $this->getFolderIdByLogin($this->externalLogin); @@ -464,7 +452,7 @@ public function useExistingVC($obj_id, $sco_id) throw new ilException('xavc_meeting_creation_error'); } - if(!$this->xmlApi->getName($sco_id, $folder_id, $session)) + if(!$this->xmlApi->getName($sco_id, $folder_id)) { throw new ilException('xavc_meeting_not_available'); } @@ -481,12 +469,12 @@ public function useExistingVC($obj_id, $sco_id) $ilUser->getEmail(), $ilUser->getPasswd(), $ilUser->getFirstName(), - $ilUser->getLastName(), - $session + $ilUser->getLastName() + ); } - $this->xmlApi->updateMeetingParticipant($sco_id, $this->externalLogin, $session, 'host'); + $this->xmlApi->updateMeetingParticipant($sco_id, $this->externalLogin, null, 'host'); $start_date = time(); $end_date = strtotime('+2 hours'); @@ -527,19 +515,6 @@ protected function publishCreationAC() $owner_id = ilObject::_lookupOwner($obj_id); $ownerObj = new ilObjUser($owner_id); - // receive breeze session - $session = $this->xmlApi->getBreezeSession(); - if(!$session) - { - throw new ilException('xavc_connection_error'); - } - - // access check - if(!$this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { - throw new ilException('xavc_authentication_error'); - } - // receive folder id $this->externalLogin = $this->checkExternalUser($ownerObj->getId()); @@ -572,7 +547,6 @@ protected function publishCreationAC() date('Y-m-d', $end_date->getUnixTime()), date('H:i', $end_date->getUnixTime()), $folder_id, - $session, $source_sco_id, $lang ); @@ -601,22 +575,22 @@ protected function publishCreationAC() $ownerObj->getEmail(), $ownerObj->getPasswd(), $ownerObj->getFirstName(), - $ownerObj->getLastName(), - $session); + $ownerObj->getLastName() + ); } - $this->xmlApi->updateMeetingParticipant($meeting_id, $this->externalLogin, $session, 'host'); + $this->xmlApi->updateMeetingParticipant($meeting_id, $this->externalLogin, null, 'host'); } else { //In the SWITCH aai case, every user already exists thanks to "cave" //Add ILIAS-user himself - $this->xmlApi->addMeetingHost($meeting_id, $ownerObj->getEmail(), $session); + $this->xmlApi->addMeetingHost($meeting_id, $ownerObj->getEmail(), null); //Add technical user - $this->xmlApi->updateMeetingParticipant($meeting_id, ilAdobeConnectServer::getSetting('login'), $session, 'host'); + $this->xmlApi->updateMeetingParticipant($meeting_id, ilAdobeConnectServer::getSetting('login'), null, 'host'); } - $this->xmlApi->updatePermission($meeting_id, $session, $access_level); + $this->xmlApi->updatePermission($meeting_id, null, $access_level); $ilDB->insert('rep_robj_xavc_data', array( @@ -691,12 +665,9 @@ public function addCrsGrpMembers($ref_id, $sco_id, $member_ids = null) } // receive breeze session - $session = $this->xmlApi->getBreezeSession(); $this->pluginObj->includeClass('class.ilXAVCMembers.php'); - - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { + foreach($admins as $user_id) { if($user_id == $this->getOwner()) @@ -724,7 +695,7 @@ public function addCrsGrpMembers($ref_id, $sco_id, $member_ids = null) $xavcMemberObj->insertXAVCMember(); } - $this->xmlApi->updateMeetingParticipant($sco_id, ilXAVCMembers::_lookupXAVCLogin($user_id), $session, $status); + $this->xmlApi->updateMeetingParticipant($sco_id, ilXAVCMembers::_lookupXAVCLogin($user_id), null, $status); } foreach($tutors as $user_id) @@ -754,7 +725,7 @@ public function addCrsGrpMembers($ref_id, $sco_id, $member_ids = null) $xavcMemberObj->insertXAVCMember(); } - $this->xmlApi->updateMeetingParticipant($sco_id, ilXAVCMembers::_lookupXAVCLogin($user_id), $session, $status); + $this->xmlApi->updateMeetingParticipant($sco_id, ilXAVCMembers::_lookupXAVCLogin($user_id), null, $status); } foreach($members as $user_id) @@ -783,7 +754,7 @@ public function addCrsGrpMembers($ref_id, $sco_id, $member_ids = null) $xavcMemberObj->insertXAVCMember(); } - $this->xmlApi->updateMeetingParticipant($sco_id, ilXAVCMembers::_lookupXAVCLogin($user_id), $session, $status); + $this->xmlApi->updateMeetingParticipant($sco_id, ilXAVCMembers::_lookupXAVCLogin($user_id), null, $status); } $owner_id = ilObject::_lookupOwner($oParticipants->getObjId()); @@ -808,8 +779,7 @@ public function addCrsGrpMembers($ref_id, $sco_id, $member_ids = null) $xavcMemberObj->insertXAVCMember(); } - $this->xmlApi->updateMeetingParticipant($sco_id, ilXAVCMembers::_lookupXAVCLogin($owner_id), $session, $status); - } + $this->xmlApi->updateMeetingParticipant($sco_id, ilXAVCMembers::_lookupXAVCLogin($owner_id), null, $status); } public function deleteCrsGrpMembers($sco_id, $delete_user_ids) @@ -827,13 +797,9 @@ public function deleteCrsGrpMembers($sco_id, $delete_user_ids) ilXAVCMembers::deleteXAVCMember($usr_id, $this->getRefId()); $xavc_login = ilXAVCMembers::_lookupXAVCLogin($usr_id); + - $session = $this->xmlApi->getBreezeSession(); - - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { - $this->xmlApi->deleteMeetingParticipant($sco_id, $xavc_login, $session); - } + $this->xmlApi->deleteMeetingParticipant($sco_id, $xavc_login, null); //remove from pd ilObjUser::_dropDesktopItem($usr_id, $this->getRefId(), 'xavc'); @@ -876,21 +842,17 @@ public function doRead() { #$this->ilias->raiseError($this->lng->txt("err_no_valid_sco_id_given"),$this->ilias->error_obj->MESSAGE); } - - $session = $this->xmlApi->getBreezeSession(); - - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { + //only read url via api, if url in database is empty if(!$this->url) { //the parameter meeting is used for the switchaai-case - $this->url = substr($this->xmlApi->getURL($this->sco_id, $this->folder_id, $session, 'meeting'), 0, -1); + $this->url = substr($this->xmlApi->getURL($this->sco_id, $this->folder_id, null, 'meeting'), 0, -1); } - $date_begin = $this->xmlApi->getStartDate($this->sco_id, $this->folder_id, $session); + $date_begin = $this->xmlApi->getStartDate($this->sco_id, $this->folder_id, null); $this->start_date = new ilDateTime(strtotime($date_begin), IL_CAL_UNIX); - $date_end_string = $this->xmlApi->getEndDate($this->sco_id, $this->folder_id, $session); + $date_end_string = $this->xmlApi->getEndDate($this->sco_id, $this->folder_id, null); $end_date = new ilDateTime(strtotime($date_end_string), IL_CAL_UNIX); $this->end_date = $end_date; $unix_duration = $end_date->getUnixTime() - $this->start_date->getUnixTime(); @@ -902,8 +864,8 @@ public function doRead() $this->pluginObj->includeClass('class.ilAdobeConnectContents.php'); $this->contents = new ilAdobeConnectContents(); - $this->access_level = $this->xmlApi->getPermissionId($this->sco_id, $session); - } + $this->access_level = $this->xmlApi->getPermissionId($this->sco_id, null); + $this->initParticipantsObject(); } @@ -914,18 +876,13 @@ public function doUpdate() { global $DIC; $ilDB = $DIC->database(); - - $session = $this->xmlApi->getBreezeSession(); - - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { + $end_date = new ilDateTime($this->start_date->getUnixTime() + $this->duration["hours"] * 3600 + $this->duration["minutes"] * 60, IL_CAL_UNIX); $this->xmlApi->updateMeeting($this->sco_id, $this->getTitle(), $this->getDescription(), date('Y-m-d', $this->start_date->getUnixTime()), date('H:i', $this->start_date->getUnixTime()), - date('Y-m-d', $end_date->getUnixTime()), date('H:i', $end_date->getUnixTime()), $session, $this->getAcLanguage()); + date('Y-m-d', $end_date->getUnixTime()), date('H:i', $end_date->getUnixTime()), $this->getAcLanguage()); - $this->xmlApi->updatePermission($this->sco_id, $session, $this->permission); - } + $this->xmlApi->updatePermission($this->sco_id, null, $this->permission); $ilDB->update('rep_robj_xavc_data', array( @@ -950,11 +907,7 @@ public function doDelete() global $DIC; $ilDB = $DIC->database(); - $session = $this->xmlApi->getBreezeSession(); - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { - $this->xmlApi->deleteMeeting($this->sco_id, $session); - } + $this->xmlApi->deleteMeeting($this->sco_id); $ilDB->manipulateF('DELETE FROM rep_robj_xavc_data WHERE id = %s', array('integer'), array($this->getId())); @@ -1243,19 +1196,16 @@ public function getEndDate() */ public function readContents($by_type = NULL) { - $session = $this->xmlApi->getBreezeSession(); $ids = array(); - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { - $ids = ($this->xmlApi->getContentIds($this->sco_id, $session) ? $this->xmlApi->getContentIds($this->sco_id, $session) : array()); + $ids = ($this->xmlApi->getContentIds($this->sco_id, null) ? $this->xmlApi->getContentIds($this->sco_id, null) : array()); foreach($ids as $id) { - $date_created = $this->xmlApi->getDateCreated($id, $this->sco_id, $session); + $date_created = $this->xmlApi->getDateCreated($id, $this->sco_id, null); - $date_end = $this->xmlApi->getDateEnd($id, $this->sco_id, $session); + $date_end = $this->xmlApi->getDateEnd($id, $this->sco_id, null); if($date_end == '') { $type = 'content'; @@ -1269,11 +1219,11 @@ public function readContents($by_type = NULL) { $attributes = array( "sco-id" => $id, - "name" => $this->xmlApi->getName($id, $this->sco_id, $session), - "url" => $this->xmlApi->getURL($id, $this->sco_id, $session), + "name" => $this->xmlApi->getName($id, $this->sco_id, null), + "url" => $this->xmlApi->getURL($id, $this->sco_id, null), "date-created" => new ilDateTime(substr($date_created, 0, 10) . " " . substr($date_created, 11, 8), IL_CAL_DATETIME), "date-end" => $date_end, - "description" => $this->xmlApi->getDescription($id, $this->sco_id, $session), + "description" => $this->xmlApi->getDescription($id, $this->sco_id, null), "type" => $type ); $this->contents->addContent($attributes); @@ -1281,42 +1231,29 @@ public function readContents($by_type = NULL) } return true; } - else - { - return false; - } - } /** * Reads records from Adobe Connect server */ public function readRecords() { - $session = $this->xmlApi->getBreezeSession(); - - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { - $ids = $this->xmlApi->getRecordIds($this->sco_id, $session); + $ids = $this->xmlApi->getRecordIds($this->sco_id, null); foreach($ids as $id) { - $date_created = $this->xmlApi->getDateCreated($id, $this->sco_id, $session); + $date_created = $this->xmlApi->getDateCreated($id, $this->sco_id, null); $attributes_records = array( "sco-id" => $id, - "name" => $this->xmlApi->getName($id, $this->getScoId(), $session), - "url" => $this->xmlApi->getURL($id, $this->sco_id, $session), + "name" => $this->xmlApi->getName($id, $this->getScoId(), null), + "url" => $this->xmlApi->getURL($id, $this->sco_id, null), "date-created" => new ilDateTime(substr($date_created, 0, 10) . " " . substr($date_created, 11, 8), IL_CAL_DATETIME), - "duration" => $this->xmlApi->getDuration($id, $this->sco_id, $session), - "description" => $this->xmlApi->getDescription($id, $this->sco_id, $session), + "duration" => $this->xmlApi->getDuration($id, $this->sco_id, null), + "description" => $this->xmlApi->getDescription($id, $this->sco_id, null), "type" => "record" ); $this->contents->addContent($attributes_records); } return true; - } - else - { - return false; - } + } /** @@ -1350,12 +1287,8 @@ public function getContent($sco_id) */ public function addContent($title = "untitled", $description = "") { - $session = $this->xmlApi->getBreezeSession(); - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { - return $this->xmlApi->addContent($this->sco_id, $title, $description, $session); - } + return $this->xmlApi->addContent($this->sco_id, $title, $description, null); } /** @@ -1367,12 +1300,7 @@ public function addContent($title = "untitled", $description = "") */ public function updateContent($sco_id, $title, $description) { - $session = $this->xmlApi->getBreezeSession(); - - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { - $this->xmlApi->updateContent($sco_id, $title, $description, $session); - } + $this->xmlApi->updateContent($sco_id, $title, $description, null); } /** @@ -1381,12 +1309,7 @@ public function updateContent($sco_id, $title, $description) */ public function deleteContent($sco_id) { - $session = $this->xmlApi->getBreezeSession(); - - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { - $this->xmlApi->deleteContent($sco_id, $session); - } + $this->xmlApi->deleteContent($sco_id, null); } /** @@ -1396,10 +1319,7 @@ public function deleteContent($sco_id) */ public function uploadContent($sco_id) { - $session = $this->xmlApi->getBreezeSession(); - - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - return $this->xmlApi->uploadContent($sco_id, $session); + return $this->xmlApi->uploadContent($sco_id, null); } /* @@ -1412,16 +1332,8 @@ public function uploadContent($sco_id) */ public function getParticipants() { - $session = $this->xmlApi->getBreezeSession(); - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { - return $this->xmlApi->getMeetingsParticipants($this->sco_id, $session); - } - else - { - return NULL; - } + return $this->xmlApi->getMeetingsParticipants($this->sco_id, null); } /** @@ -1431,15 +1343,11 @@ public function getParticipants() */ public function addParticipant($login) { - $session = $this->xmlApi->getBreezeSession(); - //check if adobe connect account exists - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { - $participant = $this->xmlApi->addMeetingParticipant($this->sco_id, $login, $session); + $participant = $this->xmlApi->addMeetingParticipant($this->sco_id, $login, null); return $participant; } - } + /** * Add a new participant to the meeting @@ -1449,19 +1357,13 @@ public function addParticipant($login) */ public function addSwitchParticipant($login, $status) { - $session = $this->xmlApi->getBreezeSession(); - $participant = $this->xmlApi->updateMeetingParticipantByTechnicalUser($this->getScoId(), $login, $session, $status); + $participant = $this->xmlApi->updateMeetingParticipantByTechnicalUser($this->getScoId(), $login, null, $status); return $participant; } public function updateParticipant($login, $permission) { - $session = $this->xmlApi->getBreezeSession(); - - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { - return $this->xmlApi->updateMeetingParticipant($this->sco_id, $login, $session, $permission); - } + return $this->xmlApi->updateMeetingParticipant($this->sco_id, $login, null, $permission); } /** @@ -1471,10 +1373,8 @@ public function updateParticipant($login, $permission) */ public function deleteParticipant($login) { - $session = $this->xmlApi->getBreezeSession(); - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - return $this->xmlApi->deleteMeetingParticipant($this->sco_id, $login, $session); + return $this->xmlApi->deleteMeetingParticipant($this->sco_id, $login, null); } /** @@ -1484,20 +1384,12 @@ public function deleteParticipant($login) */ public function isParticipant($login) { - $session = $this->xmlApi->getBreezeSession(); - - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - return $this->xmlApi->isParticipant($login, $this->sco_id, $session); + return $this->xmlApi->isParticipant($login, $this->sco_id, null); } public function getPermissionId() { - $session = $this->xmlApi->getBreezeSession(); - - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { - $permission = $this->xmlApi->getPermissionId($this->sco_id, $session); - } + $permission = $this->xmlApi->getPermissionId($this->sco_id, null); return $permission; } @@ -1629,36 +1521,31 @@ public static function _lookupScoId($a_obj_id) public static function getScosByFolderId($folder_id) { - $instance = ilAdobeConnectServer::_getInstance(); - $adminLogin = $instance->getLogin(); - $adminPass = $instance->getPasswd(); +// $instance = ilAdobeConnectServer::_getInstance(); +// $adminLogin = $instance->getLogin(); +// $adminPass = $instance->getPasswd(); $xmlApi = ilXMLApiFactory::getApiByAuthMode(); - $session = $xmlApi->getBreezeSession(); - - if($session != NULL && $xmlApi->login($adminLogin, $adminPass, $session)) - { - $scos = $xmlApi->getScosByFolderId($folder_id, $session); - } + $scos = $xmlApi->getScosByFolderId($folder_id, null); return $scos; } public function getFolderIdByLogin($externalLogin) { - $session = $this->xmlApi->getBreezeSession(); + if(ilAdobeConnectServer::getSetting('use_user_folders') == 1) { - $folder_id = $this->xmlApi->lookupUserFolderId($externalLogin, $session); + $folder_id = $this->xmlApi->lookupUserFolderId($externalLogin, null); if(!$folder_id) { - $folder_id = $this->xmlApi->createUserFolder($externalLogin, $session); + $folder_id = $this->xmlApi->createUserFolder($externalLogin, null); } } else { - $folder_id = $this->xmlApi->getShortcuts("my-meetings", $session); + $folder_id = $this->xmlApi->getShortcuts("my-meetings", null); } return $folder_id; } @@ -1669,13 +1556,9 @@ public function getFolderIdByLogin($externalLogin) */ public function getContentIconAttribute($sco_id) { - $session = $this->xmlApi->getBreezeSession(); $icons = array(); - if($session != NULL && $this->xmlApi->login($this->adminLogin, $this->adminPass, $session)) - { - $icons[] = $this->xmlApi->getContentIconAttribute($sco_id, $this->sco_id, $session); - } + $icons[] = $this->xmlApi->getContentIconAttribute($sco_id, $this->sco_id, null); return $icons; } @@ -1749,7 +1632,7 @@ public function setUseMeetingTemplate($use_meeting_template) { $this->use_meeting_template = $use_meeting_template; } - + /** * @return string */ @@ -1757,7 +1640,7 @@ public function getAcLanguage() { return $this->ac_language; } - + /** * @param string $language ISO 639-1 two-letter code */ diff --git a/classes/class.ilObjAdobeConnectGUI.php b/classes/class.ilObjAdobeConnectGUI.php index 8b1a5d4..ecee219 100755 --- a/classes/class.ilObjAdobeConnectGUI.php +++ b/classes/class.ilObjAdobeConnectGUI.php @@ -737,9 +737,16 @@ public function performSso() $presentation_url = ilAdobeConnectServer::getPresentationUrl(); - $xmlAPI->logout( $_SESSION['xavc_last_sso_sessid'] ); //login current user session - $session = $ilAdobeConnectUser->loginUser(); + if( !ilAdobeConnectServer::getSetting('enhanced_security_mode') == false || $settings->getAuthMode() == ilAdobeConnectServer::AUTH_MODE_DFN) + { + $session = $xmlAPI->getBreezeSession(true); + } + else + { + $session = $xmlAPI->generateUserSessionCookie($xavc_login); + } + $_SESSION['xavc_last_sso_sessid'] = $session; $url = $presentation_url.$this->object->getURL().'?session='.$session; @@ -1237,8 +1244,9 @@ public function requestAdobeConnectContent() $ilAdobeConnectUser->ensureAccountExistance(); $xmlAPI = ilXMLApiFactory::getApiByAuthMode(); - $xmlAPI->logout( $_SESSION['xavc_last_sso_sessid'] ); - $session = $ilAdobeConnectUser->loginUser(); + $xavc_login = $ilAdobeConnectUser->getXAVCLogin(); + //login current user session + $session = $xmlAPI->generateUserSessionCookie($xavc_login); $_SESSION['xavc_last_sso_sessid'] = $session; $url = ilUtil::appendUrlParameterString($url, 'session=' . $session); diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index fb94975..058cf7a 100755 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -304,4 +304,6 @@ members_gallery#:#Mitgliedergalerie use_meeting_template#:#Veranstaltungsvorlage verwenden use_meeting_template_info#:#Wenn dies aktiviert ist, kann eine existierende Veranstaltungsvorlage ausgewählt werden, die bei der Erstellung verwendet werden kann. shared_meeting_templates#:#Gemeinsame Vorlagen -my_meeting_templates#:#Meine Vorlagen \ No newline at end of file +my_meeting_templates#:#Meine Vorlagen +enhanced_security_mode#:#Erweiterte Sicherheit +enhanced_security_mode_info#:#Erweiterte Sicherheit wird ab Adobe Connect Version 9.0.4 unterstützt \ No newline at end of file diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index b922b6b..c9183ca 100755 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -299,4 +299,6 @@ members_gallery#:#Members Gallery use_meeting_templates#:#Use meeting templates use_meeting_templates_info#:#If aktivated, an existing meeting template can be selected for meeting room creation. shared_meeting_templates#:#Shared Templates -my_meeting_templates#:#My Templates \ No newline at end of file +my_meeting_templates#:#My Templates +enhanced_security_mode#:#Enhanced Security +enhanced_security_mode_info#:#Enhanced security is supported since Adobe Connect version 9.0.4 \ No newline at end of file diff --git a/libs/composer/composer.json b/libs/composer/composer.json new file mode 100644 index 0000000..6aa07b2 --- /dev/null +++ b/libs/composer/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "nategood/httpful": "^0.2.20" + } +} diff --git a/libs/composer/composer.lock b/libs/composer/composer.lock new file mode 100644 index 0000000..31c6a4a --- /dev/null +++ b/libs/composer/composer.lock @@ -0,0 +1,68 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "fef76c898a2830bca52ecf2891a0bd56", + "packages": [ + { + "name": "nategood/httpful", + "version": "0.2.20", + "source": { + "type": "git", + "url": "https://github.com/nategood/httpful.git", + "reference": "c1cd4d46a4b281229032cf39d4dd852f9887c0f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nategood/httpful/zipball/c1cd4d46a4b281229032cf39d4dd852f9887c0f6", + "reference": "c1cd4d46a4b281229032cf39d4dd852f9887c0f6", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "type": "library", + "autoload": { + "psr-0": { + "Httpful": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nate Good", + "email": "me@nategood.com", + "homepage": "http://nategood.com" + } + ], + "description": "A Readable, Chainable, REST friendly, PHP HTTP Client", + "homepage": "http://github.com/nategood/httpful", + "keywords": [ + "api", + "curl", + "http", + "requests", + "rest", + "restful" + ], + "time": "2015-10-26T16:11:30+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/libs/composer/vendor/autoload.php b/libs/composer/vendor/autoload.php new file mode 100644 index 0000000..ee3bfaf --- /dev/null +++ b/libs/composer/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath.'\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/libs/composer/vendor/composer/LICENSE b/libs/composer/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/libs/composer/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/libs/composer/vendor/composer/autoload_classmap.php b/libs/composer/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..7a91153 --- /dev/null +++ b/libs/composer/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + array($vendorDir . '/nategood/httpful/src'), +); diff --git a/libs/composer/vendor/composer/autoload_psr4.php b/libs/composer/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000..b265c64 --- /dev/null +++ b/libs/composer/vendor/composer/autoload_psr4.php @@ -0,0 +1,9 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit89bac2e21d431f323e16cbe0bd3d271b::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + return $loader; + } +} diff --git a/libs/composer/vendor/composer/autoload_static.php b/libs/composer/vendor/composer/autoload_static.php new file mode 100644 index 0000000..774d97d --- /dev/null +++ b/libs/composer/vendor/composer/autoload_static.php @@ -0,0 +1,26 @@ + + array ( + 'Httpful' => + array ( + 0 => __DIR__ . '/..' . '/nategood/httpful/src', + ), + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixesPsr0 = ComposerStaticInit89bac2e21d431f323e16cbe0bd3d271b::$prefixesPsr0; + + }, null, ClassLoader::class); + } +} diff --git a/libs/composer/vendor/composer/installed.json b/libs/composer/vendor/composer/installed.json new file mode 100644 index 0000000..4e5227b --- /dev/null +++ b/libs/composer/vendor/composer/installed.json @@ -0,0 +1,54 @@ +[ + { + "name": "nategood/httpful", + "version": "0.2.20", + "version_normalized": "0.2.20.0", + "source": { + "type": "git", + "url": "https://github.com/nategood/httpful.git", + "reference": "c1cd4d46a4b281229032cf39d4dd852f9887c0f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nategood/httpful/zipball/c1cd4d46a4b281229032cf39d4dd852f9887c0f6", + "reference": "c1cd4d46a4b281229032cf39d4dd852f9887c0f6", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "time": "2015-10-26T16:11:30+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Httpful": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nate Good", + "email": "me@nategood.com", + "homepage": "http://nategood.com" + } + ], + "description": "A Readable, Chainable, REST friendly, PHP HTTP Client", + "homepage": "http://github.com/nategood/httpful", + "keywords": [ + "api", + "curl", + "http", + "requests", + "rest", + "restful" + ] + } +] diff --git a/libs/composer/vendor/nategood/httpful/.gitignore b/libs/composer/vendor/nategood/httpful/.gitignore new file mode 100644 index 0000000..6cf3a90 --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +composer.lock +vendor +downloads +.idea/* diff --git a/libs/composer/vendor/nategood/httpful/.travis.yml b/libs/composer/vendor/nategood/httpful/.travis.yml new file mode 100644 index 0000000..d962e3a --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/.travis.yml @@ -0,0 +1,17 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + +matrix: + fast_finish: true + allow_failures: + - php: 7.0 + +script: + - phpunit -c ./tests/phpunit.xml diff --git a/libs/composer/vendor/nategood/httpful/LICENSE.txt b/libs/composer/vendor/nategood/httpful/LICENSE.txt new file mode 100644 index 0000000..9089270 --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright (c) 2012 Nate Good + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/libs/composer/vendor/nategood/httpful/README.md b/libs/composer/vendor/nategood/httpful/README.md new file mode 100644 index 0000000..705d8cc --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/README.md @@ -0,0 +1,228 @@ +# Httpful + +[![Build Status](https://secure.travis-ci.org/nategood/httpful.png?branch=master)](http://travis-ci.org/nategood/httpful) [![Total Downloads](https://poser.pugx.org/nategood/httpful/downloads.png)](https://packagist.org/packages/nategood/httpful) + +[Httpful](http://phphttpclient.com) is a simple Http Client library for PHP 5.3+. There is an emphasis of readability, simplicity, and flexibility – basically provide the features and flexibility to get the job done and make those features really easy to use. + +Features + + - Readable HTTP Method Support (GET, PUT, POST, DELETE, HEAD, PATCH and OPTIONS) + - Custom Headers + - Automatic "Smart" Parsing + - Automatic Payload Serialization + - Basic Auth + - Client Side Certificate Auth + - Request "Templates" + +# Sneak Peak + +Here's something to whet your appetite. Search the twitter API for tweets containing "#PHP". Include a trivial header for the heck of it. Notice that the library automatically interprets the response as JSON (can override this if desired) and parses it as an array of objects. + +```php + +// Make a request to the GitHub API with a custom +// header of "X-Trvial-Header: Just as a demo". +$url = "https://api.github.com/users/nategood"; +$response = \Httpful\Request::get($url) + ->expectsJson() + ->withXTrivialHeader('Just as a demo') + ->send(); + +echo "{$response->body->name} joined GitHub on " . + date('M jS', strtotime($response->body->created_at)) ."\n"; +``` + +# Installation + +## Phar + +A [PHP Archive](http://php.net/manual/en/book.phar.php) (or .phar) file is available for [downloading](http://phphttpclient.com/httpful.phar). Simply [download](http://phphttpclient.com/httpful.phar) the .phar, drop it into your project, and include it like you would any other php file. _This method is ideal for smaller projects, one off scripts, and quick API hacking_. + +```php +include('httpful.phar'); +$r = \Httpful\Request::get($uri)->sendIt(); +... +``` + +## Composer + +Httpful is PSR-0 compliant and can be installed using [composer](http://getcomposer.org/). Simply add `nategood/httpful` to your composer.json file. _Composer is the sane alternative to PEAR. It is excellent for managing dependencies in larger projects_. + + { + "require": { + "nategood/httpful": "*" + } + } + +## Install from Source + +Because Httpful is PSR-0 compliant, you can also just clone the Httpful repository and use a PSR-0 compatible autoloader to load the library, like [Symfony's](http://symfony.com/doc/current/components/class_loader.html). Alternatively you can use the PSR-0 compliant autoloader included with the Httpful (simply `require("bootstrap.php")`). + +## Build your Phar + +If you want the build your own [Phar Archive](http://php.net/manual/en/book.phar.php) you can use the `build` script included. +Make sure that your `php.ini` has the *Off* or 0 value for the `phar.readonly` setting. +Also you need to create ad empty `downloads` directory in the project root. + +# Show Me More! + +You can checkout the [Httpful Landing Page](http://phphttpclient.com) for more info including many examples and [documentation](http://phphttpclient.com/docs). + +# Contributing + +Httpful highly encourages sending in pull requests. When submitting a pull request please: + + - All pull requests should target the `dev` branch (not `master`) + - Make sure your code follows the [coding conventions](http://pear.php.net/manual/en/standards.php) + - Please use soft tabs (four spaces) instead of hard tabs + - Make sure you add appropriate test coverage for your changes + - Run all unit tests in the test directory via `phpunit ./tests` + - Include commenting where appropriate and add a descriptive pull request message + +# Changelog + +## 0.2.20 + + - MINOR Move Response building logic into separate function [PR #193](https://github.com/nategood/httpful/pull/193) + +## 0.2.19 + + - FEATURE Before send hook [PR #164](https://github.com/nategood/httpful/pull/164) + - MINOR More descriptive connection exceptions [PR #166](https://github.com/nategood/httpful/pull/166) + +## 0.2.18 + + - FIX [PR #149](https://github.com/nategood/httpful/pull/149) + - FIX [PR #150](https://github.com/nategood/httpful/pull/150) + - FIX [PR #156](https://github.com/nategood/httpful/pull/156) + +## 0.2.17 + + - FEATURE [PR #144](https://github.com/nategood/httpful/pull/144) Adds additional parameter to the Response class to specify additional meta data about the request/response (e.g. number of redirect). + +## 0.2.16 + + - FEATURE Added support for whenError to define a custom callback to be fired upon error. Useful for logging or overriding the default error_log behavior. + +## 0.2.15 + + - FEATURE [I #131](https://github.com/nategood/httpful/pull/131) Support for SOCKS proxy + +## 0.2.14 + + - FEATURE [I #138](https://github.com/nategood/httpful/pull/138) Added alternative option for XML request construction. In the next major release this will likely supplant the older version. + +## 0.2.13 + + - REFACTOR [I #121](https://github.com/nategood/httpful/pull/121) Throw more descriptive exception on curl errors + - REFACTOR [I #122](https://github.com/nategood/httpful/issues/122) Better proxy scrubbing in Request + - REFACTOR [I #119](https://github.com/nategood/httpful/issues/119) Better document the mimeType param on Request::body + - Misc code and test cleanup + +## 0.2.12 + + - REFACTOR [I #123](https://github.com/nategood/httpful/pull/123) Support new curl file upload method + - FEATURE [I #118](https://github.com/nategood/httpful/pull/118) 5.4 HTTP Test Server + - FIX [I #109](https://github.com/nategood/httpful/pull/109) Typo + - FIX [I #103](https://github.com/nategood/httpful/pull/103) Handle also CURLOPT_SSL_VERIFYHOST for strictSsl mode + +## 0.2.11 + + - FIX [I #99](https://github.com/nategood/httpful/pull/99) Prevent hanging on HEAD requests + +## 0.2.10 + + - FIX [I #93](https://github.com/nategood/httpful/pull/86) Fixes edge case where content-length would be set incorrectly + +## 0.2.9 + + - FEATURE [I #89](https://github.com/nategood/httpful/pull/89) multipart/form-data support (a.k.a. file uploads)! Thanks @dtelaroli! + +## 0.2.8 + + - FIX Notice fix for Pull Request 86 + +## 0.2.7 + + - FIX [I #86](https://github.com/nategood/httpful/pull/86) Remove Connection Established header when using a proxy + +## 0.2.6 + + - FIX [I #85](https://github.com/nategood/httpful/issues/85) Empty Content Length issue resolved + +## 0.2.5 + + - FEATURE [I #80](https://github.com/nategood/httpful/issues/80) [I #81](https://github.com/nategood/httpful/issues/81) Proxy support added with `useProxy` method. + +## 0.2.4 + + - FEATURE [I #77](https://github.com/nategood/httpful/issues/77) Convenience method for setting a timeout (seconds) `$req->timeoutIn(10);` + - FIX [I #75](https://github.com/nategood/httpful/issues/75) [I #78](https://github.com/nategood/httpful/issues/78) Bug with checking if digest auth is being used. + +## 0.2.3 + + - FIX Overriding default Mime Handlers + - FIX [PR #73](https://github.com/nategood/httpful/pull/73) Parsing http status codes + +## 0.2.2 + + - FEATURE Add support for parsing JSON responses as associative arrays instead of objects + - FEATURE Better support for setting constructor arguments on Mime Handlers + +## 0.2.1 + + - FEATURE [PR #72](https://github.com/nategood/httpful/pull/72) Allow support for custom Accept header + +## 0.2.0 + + - REFACTOR [PR #49](https://github.com/nategood/httpful/pull/49) Broke headers out into their own class + - REFACTOR [PR #54](https://github.com/nategood/httpful/pull/54) Added more specific Exceptions + - FIX [PR #58](https://github.com/nategood/httpful/pull/58) Fixes throwing an error on an empty xml response + - FEATURE [PR #57](https://github.com/nategood/httpful/pull/57) Adds support for digest authentication + +## 0.1.6 + + - Ability to set the number of max redirects via overloading `followRedirects(int max_redirects)` + - Standards Compliant fix to `Accepts` header + - Bug fix for bootstrap process when installed via Composer + +## 0.1.5 + + - Use `DIRECTORY_SEPARATOR` constant [PR #33](https://github.com/nategood/httpful/pull/32) + - [PR #35](https://github.com/nategood/httpful/pull/35) + - Added the raw\_headers property reference to response. + - Compose request header and added raw\_header to Request object. + - Fixed response has errors and added more comments for clarity. + - Fixed header parsing to allow the minimum (status line only) and also cater for the actual CRLF ended headers as per RFC2616. + - Added the perfect test Accept: header for all Acceptable scenarios see @b78e9e82cd9614fbe137c01bde9439c4e16ca323 for details. + - Added default User-Agent header + - `User-Agent: Httpful/0.1.5` + curl version + server software + PHP version + - To bypass this "default" operation simply add a User-Agent to the request headers even a blank User-Agent is sufficient and more than simple enough to produce me thinks. + - Completed test units for additions. + - Added phpunit coverage reporting and helped phpunit auto locate the tests a bit easier. + +## 0.1.4 + + - Add support for CSV Handling [PR #32](https://github.com/nategood/httpful/pull/32) + +## 0.1.3 + + - Handle empty responses in JsonParser and XmlParser + +## 0.1.2 + + - Added support for setting XMLHandler configuration options + - Added examples for overriding XmlHandler and registering a custom parser + - Removed the httpful.php download (deprecated in favor of httpful.phar) + +## 0.1.1 + + - Bug fix serialization default case and phpunit tests + +## 0.1.0 + + - Added Support for Registering Mime Handlers + - Created AbstractMimeHandler type that all Mime Handlers must extend + - Pulled out the parsing/serializing logic from the Request/Response classes into their own MimeHandler classes + - Added ability to register new mime handlers for mime types + diff --git a/libs/composer/vendor/nategood/httpful/bootstrap.php b/libs/composer/vendor/nategood/httpful/bootstrap.php new file mode 100644 index 0000000..10f2a7c --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/bootstrap.php @@ -0,0 +1,4 @@ +setStub($stub); +} catch(Exception $e) { + $phar = false; +} +exit_unless($phar, "Unable to create a phar. Make certain you have phar.readonly=0 set in your ini file."); +$phar->buildFromDirectory(dirname($source_dir)); +echo "[ OK ]\n"; + + + +// Add it to git! +//echo "Adding httpful.phar to the repo... "; +//$return_code = 0; +//passthru("git add $phar_path", $return_code); +//exit_unless($return_code === 0, "Unable to add download files to git."); +//echo "[ OK ]\n"; +echo "\nBuild completed successfully.\n\n"; diff --git a/libs/composer/vendor/nategood/httpful/composer.json b/libs/composer/vendor/nategood/httpful/composer.json new file mode 100644 index 0000000..dd7a549 --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/composer.json @@ -0,0 +1,27 @@ +{ + "name": "nategood/httpful", + "description": "A Readable, Chainable, REST friendly, PHP HTTP Client", + "homepage": "http://github.com/nategood/httpful", + "license": "MIT", + "keywords": ["http", "curl", "rest", "restful", "api", "requests"], + "version": "0.2.20", + "authors": [ + { + "name": "Nate Good", + "email": "me@nategood.com", + "homepage": "http://nategood.com" + } + ], + "require": { + "php": ">=5.3", + "ext-curl": "*" + }, + "autoload": { + "psr-0": { + "Httpful": "src/" + } + }, + "require-dev": { + "phpunit/phpunit": "*" + } +} diff --git a/libs/composer/vendor/nategood/httpful/examples/freebase.php b/libs/composer/vendor/nategood/httpful/examples/freebase.php new file mode 100644 index 0000000..bb3b528 --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/examples/freebase.php @@ -0,0 +1,12 @@ +expectsJson() + ->sendIt(); + +echo 'The Dead Weather has ' . count($response->body->result->album) . " albums.\n"; \ No newline at end of file diff --git a/libs/composer/vendor/nategood/httpful/examples/github.php b/libs/composer/vendor/nategood/httpful/examples/github.php new file mode 100644 index 0000000..8eb3f3b --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/examples/github.php @@ -0,0 +1,9 @@ +send(); + +echo "{$request->body->name} joined GitHub on " . date('M jS', strtotime($request->body->{'created-at'})) ."\n"; \ No newline at end of file diff --git a/libs/composer/vendor/nategood/httpful/examples/override.php b/libs/composer/vendor/nategood/httpful/examples/override.php new file mode 100644 index 0000000..2c3bdd5 --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/examples/override.php @@ -0,0 +1,44 @@ + 'http://example.com'); +\Httpful\Httpful::register(\Httpful\Mime::XML, new \Httpful\Handlers\XmlHandler($conf)); + +// We can also add the parsers with our own... +class SimpleCsvHandler extends \Httpful\Handlers\MimeHandlerAdapter +{ + /** + * Takes a response body, and turns it into + * a two dimensional array. + * + * @param string $body + * @return mixed + */ + public function parse($body) + { + return str_getcsv($body); + } + + /** + * Takes a two dimensional array and turns it + * into a serialized string to include as the + * body of a request + * + * @param mixed $payload + * @return string + */ + public function serialize($payload) + { + $serialized = ''; + foreach ($payload as $line) { + $serialized .= '"' . implode('","', $line) . '"' . "\n"; + } + return $serialized; + } +} + +\Httpful\Httpful::register('text/csv', new SimpleCsvHandler()); \ No newline at end of file diff --git a/libs/composer/vendor/nategood/httpful/examples/showclix.php b/libs/composer/vendor/nategood/httpful/examples/showclix.php new file mode 100644 index 0000000..861537e --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/examples/showclix.php @@ -0,0 +1,24 @@ +expectsType('json') + ->send(); + +// Print out the event details +echo "The event {$response->body->event} will take place on {$response->body->event_start}\n"; + +// Example overriding the default JSON handler with one that encodes the response as an array +\Httpful\Httpful::register(\Httpful\Mime::JSON, new \Httpful\Handlers\JsonHandler(array('decode_as_array' => true))); + +$response = Request::get($uri) + ->expectsType('json') + ->send(); + +// Print out the event details +echo "The event {$response->body['event']} will take place on {$response->body['event_start']}\n"; \ No newline at end of file diff --git a/libs/composer/vendor/nategood/httpful/src/Httpful/Bootstrap.php b/libs/composer/vendor/nategood/httpful/src/Httpful/Bootstrap.php new file mode 100644 index 0000000..9974bcf --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/src/Httpful/Bootstrap.php @@ -0,0 +1,97 @@ + + */ +class Bootstrap +{ + + const DIR_GLUE = DIRECTORY_SEPARATOR; + const NS_GLUE = '\\'; + + public static $registered = false; + + /** + * Register the autoloader and any other setup needed + */ + public static function init() + { + spl_autoload_register(array('\Httpful\Bootstrap', 'autoload')); + self::registerHandlers(); + } + + /** + * The autoload magic (PSR-0 style) + * + * @param string $classname + */ + public static function autoload($classname) + { + self::_autoload(dirname(dirname(__FILE__)), $classname); + } + + /** + * Register the autoloader and any other setup needed + */ + public static function pharInit() + { + spl_autoload_register(array('\Httpful\Bootstrap', 'pharAutoload')); + self::registerHandlers(); + } + + /** + * Phar specific autoloader + * + * @param string $classname + */ + public static function pharAutoload($classname) + { + self::_autoload('phar://httpful.phar', $classname); + } + + /** + * @param string $base + * @param string $classname + */ + private static function _autoload($base, $classname) + { + $parts = explode(self::NS_GLUE, $classname); + $path = $base . self::DIR_GLUE . implode(self::DIR_GLUE, $parts) . '.php'; + + if (file_exists($path)) { + require_once($path); + } + } + /** + * Register default mime handlers. Is idempotent. + */ + public static function registerHandlers() + { + if (self::$registered === true) { + return; + } + + // @todo check a conf file to load from that instead of + // hardcoding into the library? + $handlers = array( + \Httpful\Mime::JSON => new \Httpful\Handlers\JsonHandler(), + \Httpful\Mime::XML => new \Httpful\Handlers\XmlHandler(), + \Httpful\Mime::FORM => new \Httpful\Handlers\FormHandler(), + \Httpful\Mime::CSV => new \Httpful\Handlers\CsvHandler(), + ); + + foreach ($handlers as $mime => $handler) { + // Don't overwrite if the handler has already been registered + if (Httpful::hasParserRegistered($mime)) + continue; + Httpful::register($mime, $handler); + } + + self::$registered = true; + } +} diff --git a/libs/composer/vendor/nategood/httpful/src/Httpful/Exception/ConnectionErrorException.php b/libs/composer/vendor/nategood/httpful/src/Httpful/Exception/ConnectionErrorException.php new file mode 100644 index 0000000..bba73a6 --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/src/Httpful/Exception/ConnectionErrorException.php @@ -0,0 +1,7 @@ + + */ + +namespace Httpful\Handlers; + +class CsvHandler extends MimeHandlerAdapter +{ + /** + * @param string $body + * @return mixed + * @throws \Exception + */ + public function parse($body) + { + if (empty($body)) + return null; + + $parsed = array(); + $fp = fopen('data://text/plain;base64,' . base64_encode($body), 'r'); + while (($r = fgetcsv($fp)) !== FALSE) { + $parsed[] = $r; + } + + if (empty($parsed)) + throw new \Exception("Unable to parse response as CSV"); + return $parsed; + } + + /** + * @param mixed $payload + * @return string + */ + public function serialize($payload) + { + $fp = fopen('php://temp/maxmemory:'. (6*1024*1024), 'r+'); + $i = 0; + foreach ($payload as $fields) { + if($i++ == 0) { + fputcsv($fp, array_keys($fields)); + } + fputcsv($fp, $fields); + } + rewind($fp); + $data = stream_get_contents($fp); + fclose($fp); + return $data; + } +} diff --git a/libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/FormHandler.php b/libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/FormHandler.php new file mode 100644 index 0000000..fea1c37 --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/FormHandler.php @@ -0,0 +1,30 @@ + + */ + +namespace Httpful\Handlers; + +class FormHandler extends MimeHandlerAdapter +{ + /** + * @param string $body + * @return mixed + */ + public function parse($body) + { + $parsed = array(); + parse_str($body, $parsed); + return $parsed; + } + + /** + * @param mixed $payload + * @return string + */ + public function serialize($payload) + { + return http_build_query($payload, null, '&'); + } +} \ No newline at end of file diff --git a/libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/JsonHandler.php b/libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/JsonHandler.php new file mode 100644 index 0000000..ef3bee8 --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/JsonHandler.php @@ -0,0 +1,42 @@ + + */ + +namespace Httpful\Handlers; + +class JsonHandler extends MimeHandlerAdapter +{ + private $decode_as_array = false; + + public function init(array $args) + { + $this->decode_as_array = !!(array_key_exists('decode_as_array', $args) ? $args['decode_as_array'] : false); + } + + /** + * @param string $body + * @return mixed + * @throws \Exception + */ + public function parse($body) + { + $body = $this->stripBom($body); + if (empty($body)) + return null; + $parsed = json_decode($body, $this->decode_as_array); + if (is_null($parsed) && 'null' !== strtolower($body)) + throw new \Exception("Unable to parse response as JSON"); + return $parsed; + } + + /** + * @param mixed $payload + * @return string + */ + public function serialize($payload) + { + return json_encode($payload); + } +} diff --git a/libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/MimeHandlerAdapter.php b/libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/MimeHandlerAdapter.php new file mode 100644 index 0000000..e57ebb0 --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/MimeHandlerAdapter.php @@ -0,0 +1,54 @@ +init($args); + } + + /** + * Initial setup of + * @param array $args + */ + public function init(array $args) + { + } + + /** + * @param string $body + * @return mixed + */ + public function parse($body) + { + return $body; + } + + /** + * @param mixed $payload + * @return string + */ + function serialize($payload) + { + return (string) $payload; + } + + protected function stripBom($body) + { + if ( substr($body,0,3) === "\xef\xbb\xbf" ) // UTF-8 + $body = substr($body,3); + else if ( substr($body,0,4) === "\xff\xfe\x00\x00" || substr($body,0,4) === "\x00\x00\xfe\xff" ) // UTF-32 + $body = substr($body,4); + else if ( substr($body,0,2) === "\xff\xfe" || substr($body,0,2) === "\xfe\xff" ) // UTF-16 + $body = substr($body,2); + return $body; + } +} \ No newline at end of file diff --git a/libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/README.md b/libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/README.md new file mode 100644 index 0000000..5542d40 --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/README.md @@ -0,0 +1,44 @@ +# Handlers + +Handlers are simple classes that are used to parse response bodies and serialize request payloads. All Handlers must extend the `MimeHandlerAdapter` class and implement two methods: `serialize($payload)` and `parse($response)`. Let's build a very basic Handler to register for the `text/csv` mime type. + + + */ + +namespace Httpful\Handlers; + +class XHtmlHandler extends MimeHandlerAdapter +{ + // @todo add html specific parsing + // see DomDocument::load http://docs.php.net/manual/en/domdocument.loadhtml.php +} \ No newline at end of file diff --git a/libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/XmlHandler.php b/libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/XmlHandler.php new file mode 100644 index 0000000..9298a1f --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/src/Httpful/Handlers/XmlHandler.php @@ -0,0 +1,152 @@ + + * @author Nathan Good + */ + +namespace Httpful\Handlers; + +class XmlHandler extends MimeHandlerAdapter +{ + /** + * @var string $namespace xml namespace to use with simple_load_string + */ + private $namespace; + + /** + * @var int $libxml_opts see http://www.php.net/manual/en/libxml.constants.php + */ + private $libxml_opts; + + /** + * @param array $conf sets configuration options + */ + public function __construct(array $conf = array()) + { + $this->namespace = isset($conf['namespace']) ? $conf['namespace'] : ''; + $this->libxml_opts = isset($conf['libxml_opts']) ? $conf['libxml_opts'] : 0; + } + + /** + * @param string $body + * @return mixed + * @throws \Exception if unable to parse + */ + public function parse($body) + { + $body = $this->stripBom($body); + if (empty($body)) + return null; + $parsed = simplexml_load_string($body, null, $this->libxml_opts, $this->namespace); + if ($parsed === false) + throw new \Exception("Unable to parse response as XML"); + return $parsed; + } + + /** + * @param mixed $payload + * @return string + * @throws \Exception if unable to serialize + */ + public function serialize($payload) + { + list($_, $dom) = $this->_future_serializeAsXml($payload); + return $dom->saveXml(); + } + + /** + * @param mixed $payload + * @return string + * @author Ted Zellers + */ + public function serialize_clean($payload) + { + $xml = new \XMLWriter; + $xml->openMemory(); + $xml->startDocument('1.0','ISO-8859-1'); + $this->serialize_node($xml, $payload); + return $xml->outputMemory(true); + } + + /** + * @param \XMLWriter $xmlw + * @param mixed $node to serialize + * @author Ted Zellers + */ + public function serialize_node(&$xmlw, $node){ + if (!is_array($node)){ + $xmlw->text($node); + } else { + foreach ($node as $k => $v){ + $xmlw->startElement($k); + $this->serialize_node($xmlw, $v); + $xmlw->endElement(); + } + } + } + + /** + * @author Zack Douglas + */ + private function _future_serializeAsXml($value, $node = null, $dom = null) + { + if (!$dom) { + $dom = new \DOMDocument; + } + if (!$node) { + if (!is_object($value)) { + $node = $dom->createElement('response'); + $dom->appendChild($node); + } else { + $node = $dom; + } + } + if (is_object($value)) { + $objNode = $dom->createElement(get_class($value)); + $node->appendChild($objNode); + $this->_future_serializeObjectAsXml($value, $objNode, $dom); + } else if (is_array($value)) { + $arrNode = $dom->createElement('array'); + $node->appendChild($arrNode); + $this->_future_serializeArrayAsXml($value, $arrNode, $dom); + } else if (is_bool($value)) { + $node->appendChild($dom->createTextNode($value?'TRUE':'FALSE')); + } else { + $node->appendChild($dom->createTextNode($value)); + } + return array($node, $dom); + } + /** + * @author Zack Douglas + */ + private function _future_serializeArrayAsXml($value, &$parent, &$dom) + { + foreach ($value as $k => &$v) { + $n = $k; + if (is_numeric($k)) { + $n = "child-{$n}"; + } + $el = $dom->createElement($n); + $parent->appendChild($el); + $this->_future_serializeAsXml($v, $el, $dom); + } + return array($parent, $dom); + } + /** + * @author Zack Douglas + */ + private function _future_serializeObjectAsXml($value, &$parent, &$dom) + { + $refl = new \ReflectionObject($value); + foreach ($refl->getProperties() as $pr) { + if (!$pr->isPrivate()) { + $el = $dom->createElement($pr->getName()); + $parent->appendChild($el); + $this->_future_serializeAsXml($pr->getValue($value), $el, $dom); + } + } + return array($parent, $dom); + } +} \ No newline at end of file diff --git a/libs/composer/vendor/nategood/httpful/src/Httpful/Http.php b/libs/composer/vendor/nategood/httpful/src/Httpful/Http.php new file mode 100644 index 0000000..1c9aa0d --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/src/Httpful/Http.php @@ -0,0 +1,86 @@ + + */ +class Http +{ + const HEAD = 'HEAD'; + const GET = 'GET'; + const POST = 'POST'; + const PUT = 'PUT'; + const DELETE = 'DELETE'; + const PATCH = 'PATCH'; + const OPTIONS = 'OPTIONS'; + const TRACE = 'TRACE'; + + /** + * @return array of HTTP method strings + */ + public static function safeMethods() + { + return array(self::HEAD, self::GET, self::OPTIONS, self::TRACE); + } + + /** + * @param string HTTP method + * @return bool + */ + public static function isSafeMethod($method) + { + return in_array($method, self::safeMethods()); + } + + /** + * @param string HTTP method + * @return bool + */ + public static function isUnsafeMethod($method) + { + return !in_array($method, self::safeMethods()); + } + + /** + * @return array list of (always) idempotent HTTP methods + */ + public static function idempotentMethods() + { + // Though it is possible to be idempotent, POST + // is not guarunteed to be, and more often than + // not, it is not. + return array(self::HEAD, self::GET, self::PUT, self::DELETE, self::OPTIONS, self::TRACE, self::PATCH); + } + + /** + * @param string HTTP method + * @return bool + */ + public static function isIdempotent($method) + { + return in_array($method, self::safeidempotentMethodsMethods()); + } + + /** + * @param string HTTP method + * @return bool + */ + public static function isNotIdempotent($method) + { + return !in_array($method, self::idempotentMethods()); + } + + /** + * @deprecated Technically anything *can* have a body, + * they just don't have semantic meaning. So say's Roy + * http://tech.groups.yahoo.com/group/rest-discuss/message/9962 + * + * @return array of HTTP method strings + */ + public static function canHaveBody() + { + return array(self::POST, self::PUT, self::PATCH, self::OPTIONS); + } + +} \ No newline at end of file diff --git a/libs/composer/vendor/nategood/httpful/src/Httpful/Httpful.php b/libs/composer/vendor/nategood/httpful/src/Httpful/Httpful.php new file mode 100644 index 0000000..e46053d --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/src/Httpful/Httpful.php @@ -0,0 +1,47 @@ + + */ +class Mime +{ + const JSON = 'application/json'; + const XML = 'application/xml'; + const XHTML = 'application/html+xml'; + const FORM = 'application/x-www-form-urlencoded'; + const UPLOAD = 'multipart/form-data'; + const PLAIN = 'text/plain'; + const JS = 'text/javascript'; + const HTML = 'text/html'; + const YAML = 'application/x-yaml'; + const CSV = 'text/csv'; + + /** + * Map short name for a mime type + * to a full proper mime type + */ + public static $mimes = array( + 'json' => self::JSON, + 'xml' => self::XML, + 'form' => self::FORM, + 'plain' => self::PLAIN, + 'text' => self::PLAIN, + 'upload' => self::UPLOAD, + 'html' => self::HTML, + 'xhtml' => self::XHTML, + 'js' => self::JS, + 'javascript'=> self::JS, + 'yaml' => self::YAML, + 'csv' => self::CSV, + ); + + /** + * Get the full Mime Type name from a "short name". + * Returns the short if no mapping was found. + * @param string $short_name common name for mime type (e.g. json) + * @return string full mime type (e.g. application/json) + */ + public static function getFullMime($short_name) + { + return array_key_exists($short_name, self::$mimes) ? self::$mimes[$short_name] : $short_name; + } + + /** + * @param string $short_name + * @return bool + */ + public static function supportsMimeType($short_name) + { + return array_key_exists($short_name, self::$mimes); + } +} diff --git a/libs/composer/vendor/nategood/httpful/src/Httpful/Proxy.php b/libs/composer/vendor/nategood/httpful/src/Httpful/Proxy.php new file mode 100644 index 0000000..4ab9ea6 --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/src/Httpful/Proxy.php @@ -0,0 +1,16 @@ + + */ +class Request +{ + + // Option constants + const SERIALIZE_PAYLOAD_NEVER = 0; + const SERIALIZE_PAYLOAD_ALWAYS = 1; + const SERIALIZE_PAYLOAD_SMART = 2; + + const MAX_REDIRECTS_DEFAULT = 25; + + public $uri, + $method = Http::GET, + $headers = array(), + $raw_headers = '', + $strict_ssl = false, + $content_type, + $expected_type, + $additional_curl_opts = array(), + $auto_parse = true, + $serialize_payload_method = self::SERIALIZE_PAYLOAD_SMART, + $username, + $password, + $serialized_payload, + $payload, + $parse_callback, + $error_callback, + $send_callback, + $follow_redirects = false, + $max_redirects = self::MAX_REDIRECTS_DEFAULT, + $payload_serializers = array(); + + // Options + // private $_options = array( + // 'serialize_payload_method' => self::SERIALIZE_PAYLOAD_SMART + // 'auto_parse' => true + // ); + + // Curl Handle + public $_ch, + $_debug; + + // Template Request object + private static $_template; + + /** + * We made the constructor private to force the factory style. This was + * done to keep the syntax cleaner and better the support the idea of + * "default templates". Very basic and flexible as it is only intended + * for internal use. + * @param array $attrs hash of initial attribute values + */ + private function __construct($attrs = null) + { + if (!is_array($attrs)) return; + foreach ($attrs as $attr => $value) { + $this->$attr = $value; + } + } + + // Defaults Management + + /** + * Let's you configure default settings for this + * class from a template Request object. Simply construct a + * Request object as much as you want to and then pass it to + * this method. It will then lock in those settings from + * that template object. + * The most common of which may be default mime + * settings or strict ssl settings. + * Again some slight memory overhead incurred here but in the grand + * scheme of things as it typically only occurs once + * @param Request $template + */ + public static function ini(Request $template) + { + self::$_template = clone $template; + } + + /** + * Reset the default template back to the + * library defaults. + */ + public static function resetIni() + { + self::_initializeDefaults(); + } + + /** + * Get default for a value based on the template object + * @param string|null $attr Name of attribute (e.g. mime, headers) + * if null just return the whole template object; + * @return mixed default value + */ + public static function d($attr) + { + return isset($attr) ? self::$_template->$attr : self::$_template; + } + + // Accessors + + /** + * @return bool does the request have a timeout? + */ + public function hasTimeout() + { + return isset($this->timeout); + } + + /** + * @return bool has the internal curl request been initialized? + */ + public function hasBeenInitialized() + { + return isset($this->_ch); + } + + /** + * @return bool Is this request setup for basic auth? + */ + public function hasBasicAuth() + { + return isset($this->password) && isset($this->username); + } + + /** + * @return bool Is this request setup for digest auth? + */ + public function hasDigestAuth() + { + return isset($this->password) && isset($this->username) && $this->additional_curl_opts[CURLOPT_HTTPAUTH] == CURLAUTH_DIGEST; + } + + /** + * Specify a HTTP timeout + * @param float|int $timeout seconds to timeout the HTTP call + * @return Request + */ + public function timeout($timeout) + { + $this->timeout = $timeout; + return $this; + } + + // alias timeout + public function timeoutIn($seconds) + { + return $this->timeout($seconds); + } + + /** + * If the response is a 301 or 302 redirect, automatically + * send off another request to that location + * @param bool|int $follow follow or not to follow or maximal number of redirects + * @return Request + */ + public function followRedirects($follow = true) + { + $this->max_redirects = $follow === true ? self::MAX_REDIRECTS_DEFAULT : max(0, $follow); + $this->follow_redirects = (bool) $follow; + return $this; + } + + /** + * @see Request::followRedirects() + * @return Request + */ + public function doNotFollowRedirects() + { + return $this->followRedirects(false); + } + + /** + * Actually send off the request, and parse the response + * @return Response with parsed results + * @throws ConnectionErrorException when unable to parse or communicate w server + */ + public function send() + { + if (!$this->hasBeenInitialized()) + $this->_curlPrep(); + + $result = curl_exec($this->_ch); + + $response = $this->buildResponse($result); + + curl_close($this->_ch); + + return $response; + } + public function sendIt() + { + return $this->send(); + } + + // Setters + + /** + * @param string $uri + * @return Request + */ + public function uri($uri) + { + $this->uri = $uri; + return $this; + } + + /** + * User Basic Auth. + * Only use when over SSL/TSL/HTTPS. + * @param string $username + * @param string $password + * @return Request + */ + public function basicAuth($username, $password) + { + $this->username = $username; + $this->password = $password; + return $this; + } + // @alias of basicAuth + public function authenticateWith($username, $password) + { + return $this->basicAuth($username, $password); + } + // @alias of basicAuth + public function authenticateWithBasic($username, $password) + { + return $this->basicAuth($username, $password); + } + + // @alias of ntlmAuth + public function authenticateWithNTLM($username, $password) + { + return $this->ntlmAuth($username, $password); + } + + public function ntlmAuth($username, $password) + { + $this->addOnCurlOption(CURLOPT_HTTPAUTH, CURLAUTH_NTLM); + return $this->basicAuth($username, $password); + } + + /** + * User Digest Auth. + * @param string $username + * @param string $password + * @return Request + */ + public function digestAuth($username, $password) + { + $this->addOnCurlOption(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + return $this->basicAuth($username, $password); + } + + // @alias of digestAuth + public function authenticateWithDigest($username, $password) + { + return $this->digestAuth($username, $password); + } + + /** + * @return bool is this request setup for client side cert? + */ + public function hasClientSideCert() + { + return isset($this->client_cert) && isset($this->client_key); + } + + /** + * Use Client Side Cert Authentication + * @param string $key file path to client key + * @param string $cert file path to client cert + * @param string $passphrase for client key + * @param string $encoding default PEM + * @return Request + */ + public function clientSideCert($cert, $key, $passphrase = null, $encoding = 'PEM') + { + $this->client_cert = $cert; + $this->client_key = $key; + $this->client_passphrase = $passphrase; + $this->client_encoding = $encoding; + + return $this; + } + // @alias of basicAuth + public function authenticateWithCert($cert, $key, $passphrase = null, $encoding = 'PEM') + { + return $this->clientSideCert($cert, $key, $passphrase, $encoding); + } + + /** + * Set the body of the request + * @param mixed $payload + * @param string $mimeType currently, sets the sends AND expects mime type although this + * behavior may change in the next minor release (as it is a potential breaking change). + * @return Request + */ + public function body($payload, $mimeType = null) + { + $this->mime($mimeType); + $this->payload = $payload; + // Iserntentially don't call _serializePayload yet. Wait until + // we actually send off the request to convert payload to string. + // At that time, the `serialized_payload` is set accordingly. + return $this; + } + + /** + * Helper function to set the Content type and Expected as same in + * one swoop + * @param string $mime mime type to use for content type and expected return type + * @return Request + */ + public function mime($mime) + { + if (empty($mime)) return $this; + $this->content_type = $this->expected_type = Mime::getFullMime($mime); + if ($this->isUpload()) { + $this->neverSerializePayload(); + } + return $this; + } + // @alias of mime + public function sendsAndExpectsType($mime) + { + return $this->mime($mime); + } + // @alias of mime + public function sendsAndExpects($mime) + { + return $this->mime($mime); + } + + /** + * Set the method. Shouldn't be called often as the preferred syntax + * for instantiation is the method specific factory methods. + * @param string $method + * @return Request + */ + public function method($method) + { + if (empty($method)) return $this; + $this->method = $method; + return $this; + } + + /** + * @param string $mime + * @return Request + */ + public function expects($mime) + { + if (empty($mime)) return $this; + $this->expected_type = Mime::getFullMime($mime); + return $this; + } + // @alias of expects + public function expectsType($mime) + { + return $this->expects($mime); + } + + public function attach($files) + { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + foreach ($files as $key => $file) { + $mimeType = finfo_file($finfo, $file); + if (function_exists('curl_file_create')) { + $this->payload[$key] = curl_file_create($file, $mimeType); + } else { + $this->payload[$key] = '@' . $file; + if ($mimeType) { + $this->payload[$key] .= ';type=' . $mimeType; + } + } + } + $this->sendsType(Mime::UPLOAD); + return $this; + } + + /** + * @param string $mime + * @return Request + */ + public function contentType($mime) + { + if (empty($mime)) return $this; + $this->content_type = Mime::getFullMime($mime); + if ($this->isUpload()) { + $this->neverSerializePayload(); + } + return $this; + } + // @alias of contentType + public function sends($mime) + { + return $this->contentType($mime); + } + // @alias of contentType + public function sendsType($mime) + { + return $this->contentType($mime); + } + + /** + * Do we strictly enforce SSL verification? + * @param bool $strict + * @return Request + */ + public function strictSSL($strict) + { + $this->strict_ssl = $strict; + return $this; + } + public function withoutStrictSSL() + { + return $this->strictSSL(false); + } + public function withStrictSSL() + { + return $this->strictSSL(true); + } + + /** + * Use proxy configuration + * @param string $proxy_host Hostname or address of the proxy + * @param int $proxy_port Port of the proxy. Default 80 + * @param string $auth_type Authentication type or null. Accepted values are CURLAUTH_BASIC, CURLAUTH_NTLM. Default null, no authentication + * @param string $auth_username Authentication username. Default null + * @param string $auth_password Authentication password. Default null + * @return Request + */ + public function useProxy($proxy_host, $proxy_port = 80, $auth_type = null, $auth_username = null, $auth_password = null, $proxy_type = Proxy::HTTP) + { + $this->addOnCurlOption(CURLOPT_PROXY, "{$proxy_host}:{$proxy_port}"); + $this->addOnCurlOption(CURLOPT_PROXYTYPE, $proxy_type); + if (in_array($auth_type, array(CURLAUTH_BASIC,CURLAUTH_NTLM))) { + $this->addOnCurlOption(CURLOPT_PROXYAUTH, $auth_type) + ->addOnCurlOption(CURLOPT_PROXYUSERPWD, "{$auth_username}:{$auth_password}"); + } + return $this; + } + + /** + * Shortcut for useProxy to configure SOCKS 4 proxy + * @see Request::useProxy + * @return Request + */ + public function useSocks4Proxy($proxy_host, $proxy_port = 80, $auth_type = null, $auth_username = null, $auth_password = null) + { + return $this->useProxy($proxy_host, $proxy_port, $auth_type, $auth_username, $auth_password, Proxy::SOCKS4); + } + + /** + * Shortcut for useProxy to configure SOCKS 5 proxy + * @see Request::useProxy + * @return Request + */ + public function useSocks5Proxy($proxy_host, $proxy_port = 80, $auth_type = null, $auth_username = null, $auth_password = null) + { + return $this->useProxy($proxy_host, $proxy_port, $auth_type, $auth_username, $auth_password, Proxy::SOCKS5); + } + + /** + * @return bool is this request setup for using proxy? + */ + public function hasProxy() + { + return isset($this->additional_curl_opts[CURLOPT_PROXY]) && is_string($this->additional_curl_opts[CURLOPT_PROXY]); + } + + /** + * Determine how/if we use the built in serialization by + * setting the serialize_payload_method + * The default (SERIALIZE_PAYLOAD_SMART) is... + * - if payload is not a scalar (object/array) + * use the appropriate serialize method according to + * the Content-Type of this request. + * - if the payload IS a scalar (int, float, string, bool) + * than just return it as is. + * When this option is set SERIALIZE_PAYLOAD_ALWAYS, + * it will always use the appropriate + * serialize option regardless of whether payload is scalar or not + * When this option is set SERIALIZE_PAYLOAD_NEVER, + * it will never use any of the serialization methods. + * Really the only use for this is if you want the serialize methods + * to handle strings or not (e.g. Blah is not valid JSON, but "Blah" + * is). Forcing the serialization helps prevent that kind of error from + * happening. + * @param int $mode + * @return Request + */ + public function serializePayload($mode) + { + $this->serialize_payload_method = $mode; + return $this; + } + + /** + * @see Request::serializePayload() + * @return Request + */ + public function neverSerializePayload() + { + return $this->serializePayload(self::SERIALIZE_PAYLOAD_NEVER); + } + + /** + * This method is the default behavior + * @see Request::serializePayload() + * @return Request + */ + public function smartSerializePayload() + { + return $this->serializePayload(self::SERIALIZE_PAYLOAD_SMART); + } + + /** + * @see Request::serializePayload() + * @return Request + */ + public function alwaysSerializePayload() + { + return $this->serializePayload(self::SERIALIZE_PAYLOAD_ALWAYS); + } + + /** + * Add an additional header to the request + * Can also use the cleaner syntax of + * $Request->withMyHeaderName($my_value); + * @see Request::__call() + * + * @param string $header_name + * @param string $value + * @return Request + */ + public function addHeader($header_name, $value) + { + $this->headers[$header_name] = $value; + return $this; + } + + /** + * Add group of headers all at once. Note: This is + * here just as a convenience in very specific cases. + * The preferred "readable" way would be to leverage + * the support for custom header methods. + * @param array $headers + * @return Request + */ + public function addHeaders(array $headers) + { + foreach ($headers as $header => $value) { + $this->addHeader($header, $value); + } + return $this; + } + + /** + * @param bool $auto_parse perform automatic "smart" + * parsing based on Content-Type or "expectedType" + * If not auto parsing, Response->body returns the body + * as a string. + * @return Request + */ + public function autoParse($auto_parse = true) + { + $this->auto_parse = $auto_parse; + return $this; + } + + /** + * @see Request::autoParse() + * @return Request + */ + public function withoutAutoParsing() + { + return $this->autoParse(false); + } + + /** + * @see Request::autoParse() + * @return Request + */ + public function withAutoParsing() + { + return $this->autoParse(true); + } + + /** + * Use a custom function to parse the response. + * @param \Closure $callback Takes the raw body of + * the http response and returns a mixed + * @return Request + */ + public function parseWith(\Closure $callback) + { + $this->parse_callback = $callback; + return $this; + } + + /** + * @see Request::parseResponsesWith() + * @param \Closure $callback + * @return Request + */ + public function parseResponsesWith(\Closure $callback) + { + return $this->parseWith($callback); + } + + /** + * Callback called to handle HTTP errors. When nothing is set, defaults + * to logging via `error_log` + * @param \Closure $callback (string $error) + * @return Request + */ + public function whenError(\Closure $callback) + { + $this->error_callback = $callback; + return $this; + } + + /** + * Callback invoked after payload has been serialized but before + * the request has been built. + * @param \Closure $callback (Request $request) + * @return Request + */ + public function beforeSend(\Closure $callback) + { + $this->send_callback = $callback; + return $this; + } + + /** + * Register a callback that will be used to serialize the payload + * for a particular mime type. When using "*" for the mime + * type, it will use that parser for all responses regardless of the mime + * type. If a custom '*' and 'application/json' exist, the custom + * 'application/json' would take precedence over the '*' callback. + * + * @param string $mime mime type we're registering + * @param \Closure $callback takes one argument, $payload, + * which is the payload that we'll be + * @return Request + */ + public function registerPayloadSerializer($mime, \Closure $callback) + { + $this->payload_serializers[Mime::getFullMime($mime)] = $callback; + return $this; + } + + /** + * @see Request::registerPayloadSerializer() + * @param \Closure $callback + * @return Request + */ + public function serializePayloadWith(\Closure $callback) + { + return $this->registerPayloadSerializer('*', $callback); + } + + /** + * Magic method allows for neatly setting other headers in a + * similar syntax as the other setters. This method also allows + * for the sends* syntax. + * @param string $method "missing" method name called + * the method name called should be the name of the header that you + * are trying to set in camel case without dashes e.g. to set a + * header for Content-Type you would use contentType() or more commonly + * to add a custom header like X-My-Header, you would use xMyHeader(). + * To promote readability, you can optionally prefix these methods with + * "with" (e.g. withXMyHeader("blah") instead of xMyHeader("blah")). + * @param array $args in this case, there should only ever be 1 argument provided + * and that argument should be a string value of the header we're setting + * @return Request + */ + public function __call($method, $args) + { + // This method supports the sends* methods + // like sendsJSON, sendsForm + //!method_exists($this, $method) && + if (substr($method, 0, 5) === 'sends') { + $mime = strtolower(substr($method, 5)); + if (Mime::supportsMimeType($mime)) { + $this->sends(Mime::getFullMime($mime)); + return $this; + } + // else { + // throw new \Exception("Unsupported Content-Type $mime"); + // } + } + if (substr($method, 0, 7) === 'expects') { + $mime = strtolower(substr($method, 7)); + if (Mime::supportsMimeType($mime)) { + $this->expects(Mime::getFullMime($mime)); + return $this; + } + // else { + // throw new \Exception("Unsupported Content-Type $mime"); + // } + } + + // This method also adds the custom header support as described in the + // method comments + if (count($args) === 0) + return; + + // Strip the sugar. If it leads with "with", strip. + // This is okay because: No defined HTTP headers begin with with, + // and if you are defining a custom header, the standard is to prefix it + // with an "X-", so that should take care of any collisions. + if (substr($method, 0, 4) === 'with') + $method = substr($method, 4); + + // Precede upper case letters with dashes, uppercase the first letter of method + $header = ucwords(implode('-', preg_split('/([A-Z][^A-Z]*)/', $method, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY))); + $this->addHeader($header, $args[0]); + return $this; + } + + // Internal Functions + + /** + * This is the default template to use if no + * template has been provided. The template + * tells the class which default values to use. + * While there is a slight overhead for object + * creation once per execution (not once per + * Request instantiation), it promotes readability + * and flexibility within the class. + */ + private static function _initializeDefaults() + { + // This is the only place you will + // see this constructor syntax. It + // is only done here to prevent infinite + // recusion. Do not use this syntax elsewhere. + // It goes against the whole readability + // and transparency idea. + self::$_template = new Request(array('method' => Http::GET)); + + // This is more like it... + self::$_template + ->withoutStrictSSL(); + } + + /** + * Set the defaults on a newly instantiated object + * Doesn't copy variables prefixed with _ + * @return Request + */ + private function _setDefaults() + { + if (!isset(self::$_template)) + self::_initializeDefaults(); + foreach (self::$_template as $k=>$v) { + if ($k[0] != '_') + $this->$k = $v; + } + return $this; + } + + private function _error($error) + { + // TODO add in support for various Loggers that follow + // PSR 3 https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md + if (isset($this->error_callback)) { + $this->error_callback->__invoke($error); + } else { + error_log($error); + } + } + + /** + * Factory style constructor works nicer for chaining. This + * should also really only be used internally. The Request::get, + * Request::post syntax is preferred as it is more readable. + * @param string $method Http Method + * @param string $mime Mime Type to Use + * @return Request + */ + public static function init($method = null, $mime = null) + { + // Setup our handlers, can call it here as it's idempotent + Bootstrap::init(); + + // Setup the default template if need be + if (!isset(self::$_template)) + self::_initializeDefaults(); + + $request = new Request(); + return $request + ->_setDefaults() + ->method($method) + ->sendsType($mime) + ->expectsType($mime); + } + + /** + * Does the heavy lifting. Uses de facto HTTP + * library cURL to set up the HTTP request. + * Note: It does NOT actually send the request + * @return Request + * @throws \Exception + */ + public function _curlPrep() + { + // Check for required stuff + if (!isset($this->uri)) + throw new \Exception('Attempting to send a request before defining a URI endpoint.'); + + if (isset($this->payload)) { + $this->serialized_payload = $this->_serializePayload($this->payload); + } + + if (isset($this->send_callback)) { + call_user_func($this->send_callback, $this); + } + + $ch = curl_init($this->uri); + + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->method); + if ($this->method === Http::HEAD) { + curl_setopt($ch, CURLOPT_NOBODY, true); + } + + if ($this->hasBasicAuth()) { + curl_setopt($ch, CURLOPT_USERPWD, $this->username . ':' . $this->password); + } + + if ($this->hasClientSideCert()) { + + if (!file_exists($this->client_key)) + throw new \Exception('Could not read Client Key'); + + if (!file_exists($this->client_cert)) + throw new \Exception('Could not read Client Certificate'); + + curl_setopt($ch, CURLOPT_SSLCERTTYPE, $this->client_encoding); + curl_setopt($ch, CURLOPT_SSLKEYTYPE, $this->client_encoding); + curl_setopt($ch, CURLOPT_SSLCERT, $this->client_cert); + curl_setopt($ch, CURLOPT_SSLKEY, $this->client_key); + curl_setopt($ch, CURLOPT_SSLKEYPASSWD, $this->client_passphrase); + // curl_setopt($ch, CURLOPT_SSLCERTPASSWD, $this->client_cert_passphrase); + } + + if ($this->hasTimeout()) { + if (defined('CURLOPT_TIMEOUT_MS')) { + curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->timeout * 1000); + } else { + curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); + } + } + + if ($this->follow_redirects) { + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, $this->max_redirects); + } + + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->strict_ssl); + // zero is safe for all curl versions + $verifyValue = $this->strict_ssl + 0; + //Support for value 1 removed in cURL 7.28.1 value 2 valid in all versions + if ($verifyValue > 0) $verifyValue++; + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifyValue); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + // https://github.com/nategood/httpful/issues/84 + // set Content-Length to the size of the payload if present + if (isset($this->payload)) { + curl_setopt($ch, CURLOPT_POSTFIELDS, $this->serialized_payload); + if (!$this->isUpload()) { + $this->headers['Content-Length'] = + $this->_determineLength($this->serialized_payload); + } + } + + $headers = array(); + // https://github.com/nategood/httpful/issues/37 + // Except header removes any HTTP 1.1 Continue from response headers + $headers[] = 'Expect:'; + + if (!isset($this->headers['User-Agent'])) { + $headers[] = $this->buildUserAgent(); + } + + $headers[] = "Content-Type: {$this->content_type}"; + + // allow custom Accept header if set + if (!isset($this->headers['Accept'])) { + // http://pretty-rfc.herokuapp.com/RFC2616#header.accept + $accept = 'Accept: */*; q=0.5, text/plain; q=0.8, text/html;level=3;'; + + if (!empty($this->expected_type)) { + $accept .= "q=0.9, {$this->expected_type}"; + } + + $headers[] = $accept; + } + + // Solve a bug on squid proxy, NONE/411 when miss content length + if (!isset($this->headers['Content-Length']) && !$this->isUpload()) { + $this->headers['Content-Length'] = 0; + } + + foreach ($this->headers as $header => $value) { + $headers[] = "$header: $value"; + } + + $url = \parse_url($this->uri); + $path = (isset($url['path']) ? $url['path'] : '/').(isset($url['query']) ? '?'.$url['query'] : ''); + $this->raw_headers = "{$this->method} $path HTTP/1.1\r\n"; + $host = (isset($url['host']) ? $url['host'] : 'localhost').(isset($url['port']) ? ':'.$url['port'] : ''); + $this->raw_headers .= "Host: $host\r\n"; + $this->raw_headers .= \implode("\r\n", $headers); + $this->raw_headers .= "\r\n"; + + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + + if ($this->_debug) { + curl_setopt($ch, CURLOPT_VERBOSE, true); + } + + curl_setopt($ch, CURLOPT_HEADER, 1); + + // If there are some additional curl opts that the user wants + // to set, we can tack them in here + foreach ($this->additional_curl_opts as $curlopt => $curlval) { + curl_setopt($ch, $curlopt, $curlval); + } + + $this->_ch = $ch; + + return $this; + } + + /** + * @param string $str payload + * @return int length of payload in bytes + */ + public function _determineLength($str) + { + if (function_exists('mb_strlen')) { + return mb_strlen($str, '8bit'); + } else { + return strlen($str); + } + } + + /** + * @return bool + */ + public function isUpload() + { + return Mime::UPLOAD == $this->content_type; + } + + /** + * @return string + */ + public function buildUserAgent() + { + $user_agent = 'User-Agent: Httpful/' . Httpful::VERSION . ' (cURL/'; + $curl = \curl_version(); + + if (isset($curl['version'])) { + $user_agent .= $curl['version']; + } else { + $user_agent .= '?.?.?'; + } + + $user_agent .= ' PHP/'. PHP_VERSION . ' (' . PHP_OS . ')'; + + if (isset($_SERVER['SERVER_SOFTWARE'])) { + $user_agent .= ' ' . \preg_replace('~PHP/[\d\.]+~U', '', + $_SERVER['SERVER_SOFTWARE']); + } else { + if (isset($_SERVER['TERM_PROGRAM'])) { + $user_agent .= " {$_SERVER['TERM_PROGRAM']}"; + } + + if (isset($_SERVER['TERM_PROGRAM_VERSION'])) { + $user_agent .= "/{$_SERVER['TERM_PROGRAM_VERSION']}"; + } + } + + if (isset($_SERVER['HTTP_USER_AGENT'])) { + $user_agent .= " {$_SERVER['HTTP_USER_AGENT']}"; + } + + $user_agent .= ')'; + + return $user_agent; + } + + /** + * Takes a curl result and generates a Response from it + * @return Response + */ + public function buildResponse($result) { + if ($result === false) { + if ($curlErrorNumber = curl_errno($this->_ch)) { + $curlErrorString = curl_error($this->_ch); + $this->_error($curlErrorString); + throw new ConnectionErrorException('Unable to connect to "'.$this->uri.'": ' . $curlErrorNumber . ' ' . $curlErrorString); + } + + $this->_error('Unable to connect to "'.$this->uri.'".'); + throw new ConnectionErrorException('Unable to connect to "'.$this->uri.'".'); + } + + $info = curl_getinfo($this->_ch); + + // Remove the "HTTP/1.x 200 Connection established" string and any other headers added by proxy + $proxy_regex = "/HTTP\/1\.[01] 200 Connection established.*?\r\n\r\n/si"; + if ($this->hasProxy() && preg_match($proxy_regex, $result)) { + $result = preg_replace($proxy_regex, '', $result); + } + + $response = explode("\r\n\r\n", $result, 2 + $info['redirect_count']); + + $body = array_pop($response); + $headers = array_pop($response); + + return new Response($body, $headers, $this, $info); + } + + /** + * Semi-reluctantly added this as a way to add in curl opts + * that are not otherwise accessible from the rest of the API. + * @param string $curlopt + * @param mixed $curloptval + * @return Request + */ + public function addOnCurlOption($curlopt, $curloptval) + { + $this->additional_curl_opts[$curlopt] = $curloptval; + return $this; + } + + /** + * Turn payload from structured data into + * a string based on the current Mime type. + * This uses the auto_serialize option to determine + * it's course of action. See serialize method for more. + * Renamed from _detectPayload to _serializePayload as of + * 2012-02-15. + * + * Added in support for custom payload serializers. + * The serialize_payload_method stuff still holds true though. + * @see Request::registerPayloadSerializer() + * + * @param mixed $payload + * @return string + */ + private function _serializePayload($payload) + { + if (empty($payload) || $this->serialize_payload_method === self::SERIALIZE_PAYLOAD_NEVER) + return $payload; + + // When we are in "smart" mode, don't serialize strings/scalars, assume they are already serialized + if ($this->serialize_payload_method === self::SERIALIZE_PAYLOAD_SMART && is_scalar($payload)) + return $payload; + + // Use a custom serializer if one is registered for this mime type + if (isset($this->payload_serializers['*']) || isset($this->payload_serializers[$this->content_type])) { + $key = isset($this->payload_serializers[$this->content_type]) ? $this->content_type : '*'; + return call_user_func($this->payload_serializers[$key], $payload); + } + + return Httpful::get($this->content_type)->serialize($payload); + } + + /** + * HTTP Method Get + * @param string $uri optional uri to use + * @param string $mime expected + * @return Request + */ + public static function get($uri, $mime = null) + { + return self::init(Http::GET)->uri($uri)->mime($mime); + } + + + /** + * Like Request:::get, except that it sends off the request as well + * returning a response + * @param string $uri optional uri to use + * @param string $mime expected + * @return Response + */ + public static function getQuick($uri, $mime = null) + { + return self::get($uri, $mime)->send(); + } + + /** + * HTTP Method Post + * @param string $uri optional uri to use + * @param string $payload data to send in body of request + * @param string $mime MIME to use for Content-Type + * @return Request + */ + public static function post($uri, $payload = null, $mime = null) + { + return self::init(Http::POST)->uri($uri)->body($payload, $mime); + } + + /** + * HTTP Method Put + * @param string $uri optional uri to use + * @param string $payload data to send in body of request + * @param string $mime MIME to use for Content-Type + * @return Request + */ + public static function put($uri, $payload = null, $mime = null) + { + return self::init(Http::PUT)->uri($uri)->body($payload, $mime); + } + + /** + * HTTP Method Patch + * @param string $uri optional uri to use + * @param string $payload data to send in body of request + * @param string $mime MIME to use for Content-Type + * @return Request + */ + public static function patch($uri, $payload = null, $mime = null) + { + return self::init(Http::PATCH)->uri($uri)->body($payload, $mime); + } + + /** + * HTTP Method Delete + * @param string $uri optional uri to use + * @return Request + */ + public static function delete($uri, $mime = null) + { + return self::init(Http::DELETE)->uri($uri)->mime($mime); + } + + /** + * HTTP Method Head + * @param string $uri optional uri to use + * @return Request + */ + public static function head($uri) + { + return self::init(Http::HEAD)->uri($uri); + } + + /** + * HTTP Method Options + * @param string $uri optional uri to use + * @return Request + */ + public static function options($uri) + { + return self::init(Http::OPTIONS)->uri($uri); + } +} diff --git a/libs/composer/vendor/nategood/httpful/src/Httpful/Response.php b/libs/composer/vendor/nategood/httpful/src/Httpful/Response.php new file mode 100644 index 0000000..9e8747f --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/src/Httpful/Response.php @@ -0,0 +1,195 @@ + + */ +class Response +{ + + public $body, + $raw_body, + $headers, + $raw_headers, + $request, + $code = 0, + $content_type, + $parent_type, + $charset, + $meta_data, + $is_mime_vendor_specific = false, + $is_mime_personal = false; + + private $parsers; + + /** + * @param string $body + * @param string $headers + * @param Request $request + * @param array $meta_data + */ + public function __construct($body, $headers, Request $request, array $meta_data = array()) + { + $this->request = $request; + $this->raw_headers = $headers; + $this->raw_body = $body; + $this->meta_data = $meta_data; + + $this->code = $this->_parseCode($headers); + $this->headers = Response\Headers::fromString($headers); + + $this->_interpretHeaders(); + + $this->body = $this->_parse($body); + } + + /** + * Status Code Definitions + * + * Informational 1xx + * Successful 2xx + * Redirection 3xx + * Client Error 4xx + * Server Error 5xx + * + * http://pretty-rfc.herokuapp.com/RFC2616#status.codes + * + * @return bool Did we receive a 4xx or 5xx? + */ + public function hasErrors() + { + return $this->code >= 400; + } + + /** + * @return bool + */ + public function hasBody() + { + return !empty($this->body); + } + + /** + * Parse the response into a clean data structure + * (most often an associative array) based on the expected + * Mime type. + * @param string Http response body + * @return array|string|object the response parse accordingly + */ + public function _parse($body) + { + // If the user decided to forgo the automatic + // smart parsing, short circuit. + if (!$this->request->auto_parse) { + return $body; + } + + // If provided, use custom parsing callback + if (isset($this->request->parse_callback)) { + return call_user_func($this->request->parse_callback, $body); + } + + // Decide how to parse the body of the response in the following order + // 1. If provided, use the mime type specifically set as part of the `Request` + // 2. If a MimeHandler is registered for the content type, use it + // 3. If provided, use the "parent type" of the mime type from the response + // 4. Default to the content-type provided in the response + $parse_with = $this->request->expected_type; + if (empty($this->request->expected_type)) { + $parse_with = Httpful::hasParserRegistered($this->content_type) + ? $this->content_type + : $this->parent_type; + } + + return Httpful::get($parse_with)->parse($body); + } + + /** + * Parse text headers from response into + * array of key value pairs + * @param string $headers raw headers + * @return array parse headers + */ + public function _parseHeaders($headers) + { + $headers = preg_split("/(\r|\n)+/", $headers, -1, \PREG_SPLIT_NO_EMPTY); + $parse_headers = array(); + for ($i = 1; $i < count($headers); $i++) { + list($key, $raw_value) = explode(':', $headers[$i], 2); + $key = trim($key); + $value = trim($raw_value); + if (array_key_exists($key, $parse_headers)) { + // See HTTP RFC Sec 4.2 Paragraph 5 + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + // If a header appears more than once, it must also be able to + // be represented as a single header with a comma-separated + // list of values. We transform accordingly. + $parse_headers[$key] .= ',' . $value; + } else { + $parse_headers[$key] = $value; + } + } + return $parse_headers; + } + + public function _parseCode($headers) + { + $end = strpos($headers, "\r\n"); + if ($end === false) $end = strlen($headers); + $parts = explode(' ', substr($headers, 0, $end)); + if (count($parts) < 2 || !is_numeric($parts[1])) { + throw new \Exception("Unable to parse response code from HTTP response due to malformed response"); + } + return intval($parts[1]); + } + + /** + * After we've parse the headers, let's clean things + * up a bit and treat some headers specially + */ + public function _interpretHeaders() + { + // Parse the Content-Type and charset + $content_type = isset($this->headers['Content-Type']) ? $this->headers['Content-Type'] : ''; + $content_type = explode(';', $content_type); + + $this->content_type = $content_type[0]; + if (count($content_type) == 2 && strpos($content_type[1], '=') !== false) { + list($nill, $this->charset) = explode('=', $content_type[1]); + } + + // RFC 2616 states "text/*" Content-Types should have a default + // charset of ISO-8859-1. "application/*" and other Content-Types + // are assumed to have UTF-8 unless otherwise specified. + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1 + // http://www.w3.org/International/O-HTTP-charset.en.php + if (!isset($this->charset)) { + $this->charset = substr($this->content_type, 5) === 'text/' ? 'iso-8859-1' : 'utf-8'; + } + + // Is vendor type? Is personal type? + if (strpos($this->content_type, '/') !== false) { + list($type, $sub_type) = explode('/', $this->content_type); + $this->is_mime_vendor_specific = substr($sub_type, 0, 4) === 'vnd.'; + $this->is_mime_personal = substr($sub_type, 0, 4) === 'prs.'; + } + + // Parent type (e.g. xml for application/vnd.github.message+xml) + $this->parent_type = $this->content_type; + if (strpos($this->content_type, '+') !== false) { + list($vendor, $this->parent_type) = explode('+', $this->content_type, 2); + $this->parent_type = Mime::getFullMime($this->parent_type); + } + } + + /** + * @return string + */ + public function __toString() + { + return $this->raw_body; + } +} diff --git a/libs/composer/vendor/nategood/httpful/src/Httpful/Response/Headers.php b/libs/composer/vendor/nategood/httpful/src/Httpful/Response/Headers.php new file mode 100644 index 0000000..0c922a5 --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/src/Httpful/Response/Headers.php @@ -0,0 +1,88 @@ +headers = $headers; + } + + /** + * @param string $string + * @return Headers + */ + public static function fromString($string) + { + $lines = preg_split("/(\r|\n)+/", $string, -1, PREG_SPLIT_NO_EMPTY); + array_shift($lines); // HTTP HEADER + $headers = array(); + foreach ($lines as $line) { + list($name, $value) = explode(':', $line, 2); + $headers[strtolower(trim($name))] = trim($value); + } + return new self($headers); + } + + /** + * @param string $offset + * @return bool + */ + public function offsetExists($offset) + { + return isset($this->headers[strtolower($offset)]); + } + + /** + * @param string $offset + * @return mixed + */ + public function offsetGet($offset) + { + if (isset($this->headers[$name = strtolower($offset)])) { + return $this->headers[$name]; + } + } + + /** + * @param string $offset + * @param string $value + * @throws \Exception + */ + public function offsetSet($offset, $value) + { + throw new \Exception("Headers are read-only."); + } + + /** + * @param string $offset + * @throws \Exception + */ + public function offsetUnset($offset) + { + throw new \Exception("Headers are read-only."); + } + + /** + * @return int + */ + public function count() + { + return count($this->headers); + } + + /** + * @return array + */ + public function toArray() + { + return $this->headers; + } + +} \ No newline at end of file diff --git a/libs/composer/vendor/nategood/httpful/tests/Httpful/HttpfulTest.php b/libs/composer/vendor/nategood/httpful/tests/Httpful/HttpfulTest.php new file mode 100644 index 0000000..ad74d0d --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/tests/Httpful/HttpfulTest.php @@ -0,0 +1,604 @@ + + */ +namespace Httpful\Test; + +require(dirname(dirname(dirname(__FILE__))) . '/bootstrap.php'); +\Httpful\Bootstrap::init(); + +use Httpful\Httpful; +use Httpful\Request; +use Httpful\Mime; +use Httpful\Http; +use Httpful\Response; +use Httpful\Handlers\JsonHandler; + +define('TEST_SERVER', WEB_SERVER_HOST . ':' . WEB_SERVER_PORT); + +class HttpfulTest extends \PHPUnit_Framework_TestCase +{ + const TEST_SERVER = TEST_SERVER; + const TEST_URL = 'http://127.0.0.1:8008'; + const TEST_URL_400 = 'http://127.0.0.1:8008/400'; + + const SAMPLE_JSON_HEADER = +"HTTP/1.1 200 OK +Content-Type: application/json +Connection: keep-alive +Transfer-Encoding: chunked\r\n"; + const SAMPLE_JSON_RESPONSE = '{"key":"value","object":{"key":"value"},"array":[1,2,3,4]}'; + const SAMPLE_CSV_HEADER = +"HTTP/1.1 200 OK +Content-Type: text/csv +Connection: keep-alive +Transfer-Encoding: chunked\r\n"; + const SAMPLE_CSV_RESPONSE = +"Key1,Key2 +Value1,Value2 +\"40.0\",\"Forty\""; + const SAMPLE_XML_RESPONSE = '2a stringTRUE'; + const SAMPLE_XML_HEADER = +"HTTP/1.1 200 OK +Content-Type: application/xml +Connection: keep-alive +Transfer-Encoding: chunked\r\n"; + const SAMPLE_VENDOR_HEADER = +"HTTP/1.1 200 OK +Content-Type: application/vnd.nategood.message+xml +Connection: keep-alive +Transfer-Encoding: chunked\r\n"; + const SAMPLE_VENDOR_TYPE = "application/vnd.nategood.message+xml"; + const SAMPLE_MULTI_HEADER = +"HTTP/1.1 200 OK +Content-Type: application/json +Connection: keep-alive +Transfer-Encoding: chunked +X-My-Header:Value1 +X-My-Header:Value2\r\n"; + + function testInit() + { + $r = Request::init(); + // Did we get a 'Request' object? + $this->assertEquals('Httpful\Request', get_class($r)); + } + + function testDetermineLength() + { + $r = Request::init(); + $this->assertEquals(1, $r->_determineLength('A')); + $this->assertEquals(2, $r->_determineLength('À')); + $this->assertEquals(2, $r->_determineLength('Ab')); + $this->assertEquals(3, $r->_determineLength('Àb')); + $this->assertEquals(6, $r->_determineLength('世界')); + } + + function testMethods() + { + $valid_methods = array('get', 'post', 'delete', 'put', 'options', 'head'); + $url = 'http://example.com/'; + foreach ($valid_methods as $method) { + $r = call_user_func(array('Httpful\Request', $method), $url); + $this->assertEquals('Httpful\Request', get_class($r)); + $this->assertEquals(strtoupper($method), $r->method); + } + } + + function testDefaults() + { + // Our current defaults are as follows + $r = Request::init(); + $this->assertEquals(Http::GET, $r->method); + $this->assertFalse($r->strict_ssl); + } + + function testShortMime() + { + // Valid short ones + $this->assertEquals(Mime::JSON, Mime::getFullMime('json')); + $this->assertEquals(Mime::XML, Mime::getFullMime('xml')); + $this->assertEquals(Mime::HTML, Mime::getFullMime('html')); + $this->assertEquals(Mime::CSV, Mime::getFullMime('csv')); + $this->assertEquals(Mime::UPLOAD, Mime::getFullMime('upload')); + + // Valid long ones + $this->assertEquals(Mime::JSON, Mime::getFullMime(Mime::JSON)); + $this->assertEquals(Mime::XML, Mime::getFullMime(Mime::XML)); + $this->assertEquals(Mime::HTML, Mime::getFullMime(Mime::HTML)); + $this->assertEquals(Mime::CSV, Mime::getFullMime(Mime::CSV)); + $this->assertEquals(Mime::UPLOAD, Mime::getFullMime(Mime::UPLOAD)); + + // No false positives + $this->assertNotEquals(Mime::XML, Mime::getFullMime(Mime::HTML)); + $this->assertNotEquals(Mime::JSON, Mime::getFullMime(Mime::XML)); + $this->assertNotEquals(Mime::HTML, Mime::getFullMime(Mime::JSON)); + $this->assertNotEquals(Mime::XML, Mime::getFullMime(Mime::CSV)); + } + + function testSettingStrictSsl() + { + $r = Request::init() + ->withStrictSsl(); + + $this->assertTrue($r->strict_ssl); + + $r = Request::init() + ->withoutStrictSsl(); + + $this->assertFalse($r->strict_ssl); + } + + function testSendsAndExpectsType() + { + $r = Request::init() + ->sendsAndExpectsType(Mime::JSON); + $this->assertEquals(Mime::JSON, $r->expected_type); + $this->assertEquals(Mime::JSON, $r->content_type); + + $r = Request::init() + ->sendsAndExpectsType('html'); + $this->assertEquals(Mime::HTML, $r->expected_type); + $this->assertEquals(Mime::HTML, $r->content_type); + + $r = Request::init() + ->sendsAndExpectsType('form'); + $this->assertEquals(Mime::FORM, $r->expected_type); + $this->assertEquals(Mime::FORM, $r->content_type); + + $r = Request::init() + ->sendsAndExpectsType('application/x-www-form-urlencoded'); + $this->assertEquals(Mime::FORM, $r->expected_type); + $this->assertEquals(Mime::FORM, $r->content_type); + + $r = Request::init() + ->sendsAndExpectsType(Mime::CSV); + $this->assertEquals(Mime::CSV, $r->expected_type); + $this->assertEquals(Mime::CSV, $r->content_type); + } + + function testIni() + { + // Test setting defaults/templates + + // Create the template + $template = Request::init() + ->method(Http::POST) + ->withStrictSsl() + ->expectsType(Mime::HTML) + ->sendsType(Mime::FORM); + + Request::ini($template); + + $r = Request::init(); + + $this->assertTrue($r->strict_ssl); + $this->assertEquals(Http::POST, $r->method); + $this->assertEquals(Mime::HTML, $r->expected_type); + $this->assertEquals(Mime::FORM, $r->content_type); + + // Test the default accessor as well + $this->assertTrue(Request::d('strict_ssl')); + $this->assertEquals(Http::POST, Request::d('method')); + $this->assertEquals(Mime::HTML, Request::d('expected_type')); + $this->assertEquals(Mime::FORM, Request::d('content_type')); + + Request::resetIni(); + } + + function testAccept() + { + $r = Request::get('http://example.com/') + ->expectsType(Mime::JSON); + + $this->assertEquals(Mime::JSON, $r->expected_type); + $r->_curlPrep(); + $this->assertContains('application/json', $r->raw_headers); + } + + function testCustomAccept() + { + $accept = 'application/api-1.0+json'; + $r = Request::get('http://example.com/') + ->addHeader('Accept', $accept); + + $r->_curlPrep(); + $this->assertContains($accept, $r->raw_headers); + $this->assertEquals($accept, $r->headers['Accept']); + } + + function testUserAgent() + { + $r = Request::get('http://example.com/') + ->withUserAgent('ACME/1.2.3'); + + $this->assertArrayHasKey('User-Agent', $r->headers); + $r->_curlPrep(); + $this->assertContains('User-Agent: ACME/1.2.3', $r->raw_headers); + $this->assertNotContains('User-Agent: HttpFul/1.0', $r->raw_headers); + + $r = Request::get('http://example.com/') + ->withUserAgent(''); + + $this->assertArrayHasKey('User-Agent', $r->headers); + $r->_curlPrep(); + $this->assertContains('User-Agent:', $r->raw_headers); + $this->assertNotContains('User-Agent: HttpFul/1.0', $r->raw_headers); + } + + function testAuthSetup() + { + $username = 'nathan'; + $password = 'opensesame'; + + $r = Request::get('http://example.com/') + ->authenticateWith($username, $password); + + $this->assertEquals($username, $r->username); + $this->assertEquals($password, $r->password); + $this->assertTrue($r->hasBasicAuth()); + } + + function testDigestAuthSetup() + { + $username = 'nathan'; + $password = 'opensesame'; + + $r = Request::get('http://example.com/') + ->authenticateWithDigest($username, $password); + + $this->assertEquals($username, $r->username); + $this->assertEquals($password, $r->password); + $this->assertTrue($r->hasDigestAuth()); + } + + function testJsonResponseParse() + { + $req = Request::init()->sendsAndExpects(Mime::JSON); + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); + + $this->assertEquals("value", $response->body->key); + $this->assertEquals("value", $response->body->object->key); + $this->assertInternalType('array', $response->body->array); + $this->assertEquals(1, $response->body->array[0]); + } + + function testXMLResponseParse() + { + $req = Request::init()->sendsAndExpects(Mime::XML); + $response = new Response(self::SAMPLE_XML_RESPONSE, self::SAMPLE_XML_HEADER, $req); + $sxe = $response->body; + $this->assertEquals("object", gettype($sxe)); + $this->assertEquals("SimpleXMLElement", get_class($sxe)); + $bools = $sxe->xpath('/stdClass/boolProp'); + list( , $bool ) = each($bools); + $this->assertEquals("TRUE", (string) $bool); + $ints = $sxe->xpath('/stdClass/arrayProp/array/k1/myClass/intProp'); + list( , $int ) = each($ints); + $this->assertEquals("2", (string) $int); + $strings = $sxe->xpath('/stdClass/stringProp'); + list( , $string ) = each($strings); + $this->assertEquals("a string", (string) $string); + } + + function testCsvResponseParse() + { + $req = Request::init()->sendsAndExpects(Mime::CSV); + $response = new Response(self::SAMPLE_CSV_RESPONSE, self::SAMPLE_CSV_HEADER, $req); + + $this->assertEquals("Key1", $response->body[0][0]); + $this->assertEquals("Value1", $response->body[1][0]); + $this->assertInternalType('string', $response->body[2][0]); + $this->assertEquals("40.0", $response->body[2][0]); + } + + function testParsingContentTypeCharset() + { + $req = Request::init()->sendsAndExpects(Mime::JSON); + // $response = new Response(SAMPLE_JSON_RESPONSE, "", $req); + // // Check default content type of iso-8859-1 + $response = new Response(self::SAMPLE_JSON_RESPONSE, "HTTP/1.1 200 OK +Content-Type: text/plain; charset=utf-8\r\n", $req); + $this->assertInstanceOf('Httpful\Response\Headers', $response->headers); + $this->assertEquals($response->headers['Content-Type'], 'text/plain; charset=utf-8'); + $this->assertEquals($response->content_type, 'text/plain'); + $this->assertEquals($response->charset, 'utf-8'); + } + + function testParsingContentTypeUpload() + { + $req = Request::init(); + + $req->sendsType(Mime::UPLOAD); + // $response = new Response(SAMPLE_JSON_RESPONSE, "", $req); + // // Check default content type of iso-8859-1 + $this->assertEquals($req->content_type, 'multipart/form-data'); + } + + function testAttach() { + $req = Request::init(); + $testsPath = realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..'); + $filename = $testsPath . DIRECTORY_SEPARATOR . 'test_image.jpg'; + $req->attach(array('index' => $filename)); + $payload = $req->payload['index']; + // PHP 5.5 + will take advantage of CURLFile while previous + // versions just use the string syntax + if (is_string($payload)) { + $this->assertEquals($payload, '@' . $filename . ';type=image/jpeg'); + } else { + $this->assertInstanceOf('CURLFile', $payload); + } + + $this->assertEquals($req->content_type, Mime::UPLOAD); + $this->assertEquals($req->serialize_payload_method, Request::SERIALIZE_PAYLOAD_NEVER); + } + + function testIsUpload() { + $req = Request::init(); + + $req->sendsType(Mime::UPLOAD); + + $this->assertTrue($req->isUpload()); + } + + function testEmptyResponseParse() + { + $req = Request::init()->sendsAndExpects(Mime::JSON); + $response = new Response("", self::SAMPLE_JSON_HEADER, $req); + $this->assertEquals(null, $response->body); + + $reqXml = Request::init()->sendsAndExpects(Mime::XML); + $responseXml = new Response("", self::SAMPLE_XML_HEADER, $reqXml); + $this->assertEquals(null, $responseXml->body); + } + + function testNoAutoParse() + { + $req = Request::init()->sendsAndExpects(Mime::JSON)->withoutAutoParsing(); + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); + $this->assertInternalType('string', $response->body); + $req = Request::init()->sendsAndExpects(Mime::JSON)->withAutoParsing(); + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); + $this->assertInternalType('object', $response->body); + } + + function testParseHeaders() + { + $req = Request::init()->sendsAndExpects(Mime::JSON); + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); + $this->assertEquals('application/json', $response->headers['Content-Type']); + } + + function testRawHeaders() + { + $req = Request::init()->sendsAndExpects(Mime::JSON); + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); + $this->assertContains('Content-Type: application/json', $response->raw_headers); + } + + function testHasErrors() + { + $req = Request::init()->sendsAndExpects(Mime::JSON); + $response = new Response('', "HTTP/1.1 100 Continue\r\n", $req); + $this->assertFalse($response->hasErrors()); + $response = new Response('', "HTTP/1.1 200 OK\r\n", $req); + $this->assertFalse($response->hasErrors()); + $response = new Response('', "HTTP/1.1 300 Multiple Choices\r\n", $req); + $this->assertFalse($response->hasErrors()); + $response = new Response('', "HTTP/1.1 400 Bad Request\r\n", $req); + $this->assertTrue($response->hasErrors()); + $response = new Response('', "HTTP/1.1 500 Internal Server Error\r\n", $req); + $this->assertTrue($response->hasErrors()); + } + + function testWhenError() { + $caught = false; + + try { + Request::get('malformed:url') + ->whenError(function($error) use(&$caught) { + $caught = true; + }) + ->timeoutIn(0.1) + ->send(); + } catch (\Httpful\Exception\ConnectionErrorException $e) {} + + $this->assertTrue($caught); + } + + function testBeforeSend() { + $invoked = false; + $changed = false; + $self = $this; + + try { + Request::get('malformed://url') + ->beforeSend(function($request) use(&$invoked,$self) { + $self->assertEquals('malformed://url', $request->uri); + $self->assertEquals('A payload', $request->serialized_payload); + $request->uri('malformed2://url'); + $invoked = true; + }) + ->whenError(function($error) { /* Be silent */ }) + ->body('A payload') + ->send(); + } catch (\Httpful\Exception\ConnectionErrorException $e) { + $this->assertTrue(strpos($e->getMessage(), 'malformed2') !== false); + $changed = true; + } + + $this->assertTrue($invoked); + $this->assertTrue($changed); + } + + function test_parseCode() + { + $req = Request::init()->sendsAndExpects(Mime::JSON); + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); + $code = $response->_parseCode("HTTP/1.1 406 Not Acceptable\r\n"); + $this->assertEquals(406, $code); + } + + function testToString() + { + $req = Request::init()->sendsAndExpects(Mime::JSON); + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); + $this->assertEquals(self::SAMPLE_JSON_RESPONSE, (string)$response); + } + + function test_parseHeaders() + { + $parse_headers = Response\Headers::fromString(self::SAMPLE_JSON_HEADER); + $this->assertCount(3, $parse_headers); + $this->assertEquals('application/json', $parse_headers['Content-Type']); + $this->assertTrue(isset($parse_headers['Connection'])); + } + + function testMultiHeaders() + { + $req = Request::init(); + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_MULTI_HEADER, $req); + $parse_headers = $response->_parseHeaders(self::SAMPLE_MULTI_HEADER); + $this->assertEquals('Value1,Value2', $parse_headers['X-My-Header']); + } + + function testDetectContentType() + { + $req = Request::init(); + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); + $this->assertEquals('application/json', $response->headers['Content-Type']); + } + + function testMissingBodyContentType() + { + $body = 'A string'; + $request = Request::post(HttpfulTest::TEST_URL, $body)->_curlPrep(); + $this->assertEquals($body, $request->serialized_payload); + } + + function testParentType() + { + // Parent type + $request = Request::init()->sendsAndExpects(Mime::XML); + $response = new Response('Nathan', self::SAMPLE_VENDOR_HEADER, $request); + + $this->assertEquals("application/xml", $response->parent_type); + $this->assertEquals(self::SAMPLE_VENDOR_TYPE, $response->content_type); + $this->assertTrue($response->is_mime_vendor_specific); + + // Make sure we still parsed as if it were plain old XML + $this->assertEquals("Nathan", $response->body->name->__toString()); + } + + function testMissingContentType() + { + // Parent type + $request = Request::init()->sendsAndExpects(Mime::XML); + $response = new Response('Nathan', +"HTTP/1.1 200 OK +Connection: keep-alive +Transfer-Encoding: chunked\r\n", $request); + + $this->assertEquals("", $response->content_type); + } + + function testCustomMimeRegistering() + { + // Register new mime type handler for "application/vnd.nategood.message+xml" + Httpful::register(self::SAMPLE_VENDOR_TYPE, new DemoMimeHandler()); + + $this->assertTrue(Httpful::hasParserRegistered(self::SAMPLE_VENDOR_TYPE)); + + $request = Request::init(); + $response = new Response('Nathan', self::SAMPLE_VENDOR_HEADER, $request); + + $this->assertEquals(self::SAMPLE_VENDOR_TYPE, $response->content_type); + $this->assertEquals('custom parse', $response->body); + } + + public function testShorthandMimeDefinition() + { + $r = Request::init()->expects('json'); + $this->assertEquals(Mime::JSON, $r->expected_type); + + $r = Request::init()->expectsJson(); + $this->assertEquals(Mime::JSON, $r->expected_type); + } + + public function testOverrideXmlHandler() + { + // Lazy test... + $prev = \Httpful\Httpful::get(\Httpful\Mime::XML); + $this->assertEquals($prev, new \Httpful\Handlers\XmlHandler()); + $conf = array('namespace' => 'http://example.com'); + \Httpful\Httpful::register(\Httpful\Mime::XML, new \Httpful\Handlers\XmlHandler($conf)); + $new = \Httpful\Httpful::get(\Httpful\Mime::XML); + $this->assertNotEquals($prev, $new); + } + + public function testHasProxyWithoutProxy() + { + $r = Request::get('someUrl'); + $this->assertFalse($r->hasProxy()); + } + + public function testHasProxyWithProxy() + { + $r = Request::get('some_other_url'); + $r->useProxy('proxy.com'); + $this->assertTrue($r->hasProxy()); + } + + public function testParseJSON() + { + $handler = new JsonHandler(); + + $bodies = array( + 'foo', + array(), + array('foo', 'bar'), + null + ); + foreach ($bodies as $body) { + $this->assertEquals($body, $handler->parse(json_encode($body))); + } + + try { + $result = $handler->parse('invalid{json'); + } catch(\Exception $e) { + $this->assertEquals('Unable to parse response as JSON', $e->getMessage()); + return; + } + $this->fail('Expected an exception to be thrown due to invalid json'); + } + + // /** + // * Skeleton for testing against the 5.4 baked in server + // */ + // public function testLocalServer() + // { + // if (!defined('WITHOUT_SERVER') || (defined('WITHOUT_SERVER') && !WITHOUT_SERVER)) { + // // PHP test server seems to always set content type to application/octet-stream + // // so force parsing as JSON here + // Httpful::register('application/octet-stream', new \Httpful\Handlers\JsonHandler()); + // $response = Request::get(TEST_SERVER . '/test.json') + // ->sendsAndExpects(MIME::JSON); + // $response->send(); + // $this->assertTrue(...); + // } + // } +} + +class DemoMimeHandler extends \Httpful\Handlers\MimeHandlerAdapter +{ + public function parse($body) + { + return 'custom parse'; + } +} + diff --git a/libs/composer/vendor/nategood/httpful/tests/Httpful/requestTest.php b/libs/composer/vendor/nategood/httpful/tests/Httpful/requestTest.php new file mode 100644 index 0000000..3286765 --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/tests/Httpful/requestTest.php @@ -0,0 +1,21 @@ + + */ +namespace Httpful\Test; + +class requestTest extends \PHPUnit_Framework_TestCase +{ + + /** + * @author Nick Fox + * @expectedException Httpful\Exception\ConnectionErrorException + * @expectedExceptionMessage Unable to connect + */ + public function testGet_InvalidURL() + { + // Silence the default logger via whenError override + \Httpful\Request::get('unavailable.url')->whenError(function($error) {})->send(); + } + +} diff --git a/libs/composer/vendor/nategood/httpful/tests/bootstrap-server.php b/libs/composer/vendor/nategood/httpful/tests/bootstrap-server.php new file mode 100644 index 0000000..1c27bd9 --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/tests/bootstrap-server.php @@ -0,0 +1,42 @@ +./server.log 2>&1 & echo $!', WEB_SERVER_HOST, WEB_SERVER_PORT, WEB_SERVER_DOCROOT); + + // Execute the command and store the process ID + $output = array(); + exec($command, $output, $exit_code); + + // sleep for a second to let server come up + sleep(1); + $pid = (int) $output[0]; + + // check server.log to see if it failed to start + $server_logs = file_get_contents("./server.log"); + if (strpos($server_logs, "Fail") !== false) { + // server failed to start for some reason + print "Failed to start server! Logs:" . PHP_EOL . PHP_EOL; + print_r($server_logs); + exit(1); + } + + echo sprintf('%s - Web server started on %s:%d with PID %d', date('r'), WEB_SERVER_HOST, WEB_SERVER_PORT, $pid) . PHP_EOL; + + register_shutdown_function(function() { + // cleanup after ourselves -- remove log file, shut down server + global $pid; + unlink("./server.log"); + posix_kill($pid, SIGKILL); + }); +} diff --git a/libs/composer/vendor/nategood/httpful/tests/phpunit.xml b/libs/composer/vendor/nategood/httpful/tests/phpunit.xml new file mode 100644 index 0000000..18ab15a --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/tests/phpunit.xml @@ -0,0 +1,16 @@ + + + + . + + + + + + + + + + + + diff --git a/libs/composer/vendor/nategood/httpful/tests/static/test.json b/libs/composer/vendor/nategood/httpful/tests/static/test.json new file mode 100644 index 0000000..a628852 --- /dev/null +++ b/libs/composer/vendor/nategood/httpful/tests/static/test.json @@ -0,0 +1 @@ +{"foo": "bar", "baz": false} diff --git a/libs/composer/vendor/nategood/httpful/tests/test_image.jpg b/libs/composer/vendor/nategood/httpful/tests/test_image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3ce28ddd21269a8a3af4d0143115842202ee4297 GIT binary patch literal 52666 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%0A}C ztv4E@^14)Ecw*!jj0booc}1_vh@$!mKS2~$6?g;~X9YwZe6}HJ*So~xBEkorpp7e&zeY!`kt9(%i!mydDJvD#y+8fZ%ipTtMoKLk) zw$+vVa>i%ec}F6=&*A1qKOrw3Ut$FMKP- zZw)QPdMe7qYsC&qpkA{3x2*7^0?V~Pn9T})=4}gFAq*bFm>Oihk;9Jfvq#-uilHtL ztv9)mIi#(gm{ABriC@eu^WYKt8EmMBN5VHOxLi+)2AjLtNhCX^eZIfj(7N4yq5_v` zEh1}tqv;NUVhdVqgv^_)ixnfGZ?kRw2LOXWe7~hH<3)vb8@=c)NsLlp&=+Fvqjez$ zkg2C(U5?#BG|U<~^rFFbJ2aX0r2J^0R~?#6(tb2?MSyl_&w6fX%@zxe%_dDIX*edB z0U*ivQqxb~2cLRh#(^d+6q%zv=or0C6oAp11TRWy`H!%t46&i{l2i;6`A`IH_rdHb zM+XDHG@T#$(~97GcJ?&IV5FrTGe@wXVsxO=(T->k+=^9UyPlcmn0P1aPe}LR403Ud z&?S4Cw)aI!`EUn40dAGGYiX(6+e^0hW)Bd?agu#aa(O5MtC9swm$y;IQD>1<^#QAv zr57)R^l4(F9VVOfb}s9dbJ$9dNf=jU-JRQs =ilWy9Lt6oX62v`>&WaBi&c9s<` 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