Index: typo3/sysext/em/ext_emconf.php =================================================================== --- typo3/sysext/em/ext_emconf.php (revision 0) +++ typo3/sysext/em/ext_emconf.php (revision 0) @@ -0,0 +1,53 @@ + 'Ext Manager', + 'description' => 'TYPO3 Extension Manager', + 'category' => 'module', + 'shy' => 1, + 'dependencies' => 'cms', + 'conflicts' => '', + 'priority' => '', + 'loadOrder' => '', + 'module' => 'view', + 'doNotLoadInFE' => 1, + 'state' => 'stable', + 'internal' => 0, + 'uploadfolder' => 0, + 'createDirs' => '', + 'modify_tables' => '', + 'clearCacheOnLoad' => 0, + 'lockType' => '', + 'author' => 'Kasper Skaarhoj', + 'author_email' => 'kasperYYYY@typo3.com', + 'author_company' => '', + 'CGLcompliance' => '', + 'CGLcompliance_note' => '', + 'version' => '1.0.0', + '_md5_values_when_last_written' => 'a:6:{s:12:"ext_icon.gif";s:4:"2d41";s:14:"ext_tables.php";s:4:"6f55";s:14:"mod1/clear.gif";s:4:"cc11";s:13:"mod1/conf.php";s:4:"ff90";s:13:"mod1/func.gif";s:4:"2d41";s:14:"mod1/index.php";s:4:"69d7";}', + 'constraints' => array( + 'depends' => array( + 'cms' => '', + 'php' => '5.1.0-0.0.0', + 'typo3' => '4.5.0-0.0.0', + ), + 'conflicts' => array( + ), + 'suggests' => array( + ), + ), + 'suggests' => array( + ), +); + +?> \ No newline at end of file Index: typo3/sysext/em/ext_icon.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: typo3\sysext\em\ext_icon.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: typo3/sysext/em/ext_tables.php =================================================================== --- typo3/sysext/em/ext_tables.php (revision 0) +++ typo3/sysext/em/ext_tables.php (revision 0) @@ -0,0 +1,9 @@ + \ No newline at end of file Index: typo3/sysext/em/mod1/class.em_index.php =================================================================== --- typo3/sysext/em/mod1/class.em_index.php (revision 0) +++ typo3/sysext/em/mod1/class.em_index.php (revision 0) @@ -0,0 +1,6206 @@ + +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* A copy is found in the textfile GPL.txt and important notices to the license +* from the author is found in LICENSE.txt distributed with these scripts. +* +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ +/** + * Module: Extension manager + * + * $Id: class.em_index.php 8173 2010-07-13 17:35:35Z jsegars $ + * + * @author Kasper Skaarhoj + * @author Karsten Dambekalns + */ +/** + * [CLASS/FUNCTION INDEX of SCRIPT] + * + * + * + * 194: class SC_mod_tools_em_index extends t3lib_SCbase + * + * SECTION: Standard module initialization + * 337: function init() + * 417: function handleExternalFunctionValue($MM_key='function', $MS_value=NULL) + * 431: function menuConfig() + * 508: function main() + * 584: function printContent() + * + * SECTION: Function Menu Applications + * 609: function extensionList_loaded() + * 664: function extensionList_installed() + * 736: function extensionList_import() + * 903: function alterSettings() + * + * SECTION: Command Applications (triggered by GET var) + * 1005: function importExtInfo($extKey, $version='') + * 1062: function fetchMetaData($metaType) + * 1125: function getMirrorURL() + * 1158: function installExtension($extKey, $version=null, $mode=EM_INSTALL_VERSION_MIN) + * 1279: function importExtFromRep($extKey,$version,$loc,$uploadFlag=0,$dontDelete=0,$directInput='') + * 1425: function showExtDetails($extKey) + * + * SECTION: Application Sub-functions (HTML parts) + * 1737: function updatesForm($extKey,$extInfo,$notSilent=0,$script='',$addFields='') + * 1768: function extDumpTables($extKey,$extInfo) + * 1835: function getFileListOfExtension($extKey,$conf) + * 1889: function extDelete($extKey,$extInfo) + * 1920: function extUpdateEMCONF($extKey,$extInfo) + * 1940: function extBackup($extKey,$extInfo) + * 1987: function extBackup_dumpDataTablesLine($tablesArray,$extKey) + * 2015: function extInformationArray($extKey,$extInfo,$remote=0) + * 2097: function extInformationArray_dbReq($techInfo,$tableHeader=0) + * 2110: function extInformationArray_dbInst($dbInst,$current) + * 2129: function getRepositoryUploadForm($extKey,$extInfo) + * + * SECTION: Extension list rendering + * 2190: function extensionListRowHeader($trAttrib,$cells,$import=0) + * 2251: function extensionListRow($extKey,$extInfo,$cells,$bgColorClass='',$inst_list=array(),$import=0,$altLinkUrl='') + * + * SECTION: Output helper functions + * 2367: function wrapEmail($str,$email) + * 2380: function helpCol($key) + * 2396: function labelInfo($str) + * 2408: function extensionTitleIconHeader($extKey,$extInfo,$align='top') + * 2423: function removeButton() + * 2432: function installButton() + * 2441: function noImportMsg() + * 2454: function depToString($dep,$type='depends') + * 2473: function stringToDep($dep) + * + * SECTION: Read information about all available extensions + * 2503: function getInstalledExtensions() + * 2530: function getInstExtList($path,&$list,&$cat,$type) + * 2561: function fixEMCONF($emConf) + * 2600: function splitVersionRange($ver) + * 2616: function prepareImportExtList() + * 2660: function setCat(&$cat,$listArrayPart,$extKey) + * + * SECTION: Extension analyzing (detailed information) + * 2710: function makeDetailedExtensionAnalysis($extKey,$extInfo,$validity=0) + * 2892: function getClassIndexLocallangFiles($absPath,$table_class_prefix,$extKey) + * 2962: function modConfFileAnalysis($confFilePath) + * 2990: function serverExtensionMD5Array($extKey,$conf) + * 3015: function findMD5ArrayDiff($current,$past) + * + * SECTION: File system operations + * 3047: function createDirsInPath($dirs,$extDirPath) + * 3065: function removeExtDirectory($removePath,$removeContentOnly=0) + * 3128: function clearAndMakeExtensionDir($importedData,$type,$dontDelete=0) + * 3182: function removeCacheFiles() + * 3192: function extractDirsFromFileList($files) + * 3218: function getExtPath($extKey,$type) + * + * SECTION: Writing to "conf.php" and "localconf.php" files + * 3252: function writeTYPO3_MOD_PATH($confFilePath,$type,$mP) + * 3289: function writeNewExtensionList($newExtList) + * 3312: function writeTsStyleConfig($extKey,$arr) + * 3334: function updateLocalEM_CONF($extKey,$extInfo) + * + * SECTION: Compiling upload information, emconf-file etc. + * 3376: function construct_ext_emconf_file($extKey,$EM_CONF) + * 3407: function arrayToCode($array, $level=0) + * 3433: function makeUploadArray($extKey,$conf) + * 3502: function getSerializedLocalLang($file,$content) + * + * SECTION: Managing dependencies, conflicts, priorities, load order of extension keys + * 3538: function addExtToList($extKey,$instExtInfo) + * 3569: function checkDependencies($extKey, $conf, $instExtInfo) + * 3709: function removeExtFromList($extKey,$instExtInfo) + * 3746: function removeRequiredExtFromListArr($listArr) + * 3761: function managesPriorities($listArr,$instExtInfo) + * + * SECTION: System Update functions (based on extension requirements) + * 3813: function checkClearCache($extInfo) + * 3840: function checkUploadFolder($extKey,$extInfo) + * 3925: function checkDBupdates($extKey,$extInfo,$infoOnly=0) + * 4022: function forceDBupdates($extKey, $extInfo) + * 4080: function tsStyleConfigForm($extKey,$extInfo,$output=0,$script='',$addFields='') + * + * SECTION: Dumping database (MySQL compliant) + * 4175: function dumpTableAndFieldStructure($arr) + * 4200: function dumpStaticTables($tableList) + * 4229: function dumpHeader() + * 4246: function dumpTableHeader($table,$fieldKeyInfo,$dropTableIfExists=0) + * 4288: function dumpTableContent($table,$fieldStructure) + * 4323: function getTableAndFieldStructure($parts) + * + * SECTION: TER Communication functions + * 4373: function uploadExtensionToTER($em) + * + * SECTION: Various helper functions + * 4411: function listOrderTitle($listOrder,$key) + * 4436: function makeVersion($v,$mode) + * 4448: function renderVersion($v,$raise='') + * 4485: function ulFolder($extKey) + * 4494: function importAtAll() + * 4505: function importAsType($type,$lockType='') + * 4527: function deleteAsType($type) + * 4548: function versionDifference($v1,$v2,$div=1) + * 4560: function first_in_array($str,$array,$caseInsensitive=FALSE) + * 4578: function includeEMCONF($path,$_EXTKEY) + * 4593: function searchExtension($extKey,$row) + * + * TOTAL FUNCTIONS: 90 + * (This index is automatically created/updated by the extension "extdeveval") + * + */ + + // Include classes needed: +require_once('class.em_xmlhandler.php'); +require_once('class.em_terconnection.php'); +require_once('class.em_unzip.php'); + +$GLOBALS['LANG']->includeLLFile('EXT:lang/locallang_mod_tools_em.xml'); + + // from tx_ter by Robert Lemke +define('TX_TER_RESULT_EXTENSIONSUCCESSFULLYUPLOADED', '10504'); + +define('EM_INSTALL_VERSION_MIN', 1); +define('EM_INSTALL_VERSION_MAX', 2); +define('EM_INSTALL_VERSION_STRICT', 3); + +/** + * Module: Extension manager + * + * @author Kasper Skaarhoj + * @author Karsten Dambekalns + * @package TYPO3 + * @subpackage core + */ +class SC_mod_tools_em_index extends t3lib_SCbase { + + // Internal, static: + var $versionDiffFactor = 1; // This means that version difference testing for import is detected for sub-versions only, not dev-versions. Default: 1000 + var $systemInstall = 0; // If "1" then installs in the sysext directory is allowed. Default: 0 + var $requiredExt = ''; // List of required extension (from TYPO3_CONF_VARS) + var $maxUploadSize = 31457280; // Max size in bytes of extension upload to repository + var $kbMax = 500; // Max size in kilobytes for files to be edited. + var $doPrintContent = true; // If set (default), the function printContent() will echo the content which was collected in $this->content. You can set this to FALSE in order to echo content from elsewhere, fx. when using outbut buffering + var $listingLimit = 500; // List that many extension maximally at one time (fixing memory problems) + var $listingLimitAuthor = 250; // List that many extension maximally at one time (fixing memory problems) + + /** + * Internal variable loaded with extension categories (for display/listing). Should reflect $categories above + * Dynamic var. + */ + var $defaultCategories = Array( + 'cat' => Array ( + 'be' => array(), + 'module' => array(), + 'fe' => array(), + 'plugin' => array(), + 'misc' => array(), + 'services' => array(), + 'templates' => array(), + 'example' => array(), + 'doc' => array() + ) + ); + + var $categories = array(); // Extension Categories (static var); see init() + + var $states = array(); // Extension States; see init() + + /** + * Colors for extension states + */ + var $stateColors = Array ( + 'alpha' => '#d12438', + 'beta' => '#97b17e', + 'stable' => '#3bb65c', + 'experimental' => '#007eba', + 'test' => '#979797', + 'obsolete' => '#000000', + 'excludeFromUpdates' => '#cf7307' + ); + + /** + * "TYPE" information; labels, paths, description etc. See init() + */ + var $typeLabels = array(); + var $typeDescr = array(); + var $typePaths = Array(); // Also static, set in init() + var $typeBackPaths = Array(); // Also static, set in init() + + var $typeRelPaths = Array ( + 'S' => 'sysext/', + 'G' => 'ext/', + 'L' => '../typo3conf/ext/', + ); + + var $detailCols = Array ( + 0 => 2, + 1 => 5, + 2 => 6, + 3 => 6, + 4 => 4, + 5 => 1 + ); + + var $fe_user = array( + 'username' => '', + 'password' => '', + ); + + var $privacyNotice; // Set in init() + var $securityHint; // Set in init() + var $editTextExtensions = 'html,htm,txt,css,tmpl,inc,php,sql,conf,cnf,pl,pm,sh,xml,ChangeLog'; + var $nameSpaceExceptions = 'beuser_tracking,design_components,impexp,static_file_edit,cms,freesite,quickhelp,classic_welcome,indexed_search,sys_action,sys_workflows,sys_todos,sys_messages,direct_mail,sys_stat,tt_address,tt_board,tt_calender,tt_guest,tt_links,tt_news,tt_poll,tt_rating,tt_products,setup,taskcenter,tsconfig_help,context_help,sys_note,tstemplate,lowlevel,install,belog,beuser,phpmyadmin,aboutmodules,imagelist,setup,taskcenter,sys_notepad,viewpage,adodb'; + + + + + + // Default variables for backend modules + var $MCONF = array(); // Module configuration + var $MOD_MENU = array(); // Module menu items + var $MOD_SETTINGS = array(); // Module session settings + /** + * Document Template Object + * + * @var noDoc + */ + var $doc; + var $content; // Accumulated content + + var $inst_keys = array(); // Storage of installed extensions + var $gzcompress = 0; // Is set true, if system support compression. + + /** + * instance of TER connection handler + * + * @var SC_mod_tools_em_terconnection + */ + var $terConnection; + + /** + * XML handling class for the TYPO3 Extension Manager + * + * @var SC_mod_tools_em_xmlhandler + */ + var $xmlhandler; + var $JScode; // JavaScript code to be forwared to $this->doc->JScode + + // GPvars: + var $CMD = array(); // CMD array + var $listRemote; // If set, connects to remote repository + var $lookUpStr; // Search string when listing local extensions + + + + + /********************************* + * + * Standard module initialization + * + *********************************/ + + /** + * Standard init function of a module. + * + * @return void + */ + function init() { + global $BE_USER,$LANG,$BACK_PATH,$TYPO3_CONF_VARS; + + /** + * Extension Categories (static var) + * Content must be redundant with the same internal variable as in class.tx_extrep.php! + */ + $this->categories = array( + 'be' => $GLOBALS['LANG']->getLL('category_BE'), + 'module' => $GLOBALS['LANG']->getLL('category_BE_modules'), + 'fe' => $GLOBALS['LANG']->getLL('category_FE'), + 'plugin' => $GLOBALS['LANG']->getLL('category_FE_plugins'), + 'misc' => $GLOBALS['LANG']->getLL('category_miscellanous'), + 'services' => $GLOBALS['LANG']->getLL('category_services'), + 'templates' => $GLOBALS['LANG']->getLL('category_templates'), + 'example' => $GLOBALS['LANG']->getLL('category_examples'), + 'doc' => $GLOBALS['LANG']->getLL('category_documentation') + ); + + /** + * Extension States + * Content must be redundant with the same internal variable as in class.tx_extrep.php! + */ + $this->states = array( + 'alpha' => $GLOBALS['LANG']->getLL('state_alpha'), + 'beta' => $GLOBALS['LANG']->getLL('state_beta'), + 'stable' => $GLOBALS['LANG']->getLL('state_stable'), + 'experimental' => $GLOBALS['LANG']->getLL('state_experimental'), + 'test' => $GLOBALS['LANG']->getLL('state_test'), + 'obsolete' => $GLOBALS['LANG']->getLL('state_obsolete'), + 'excludeFromUpdates' => $GLOBALS['LANG']->getLL('state_exclude_from_updates') + ); + + /** + * "TYPE" information; labels, paths, description etc. + */ + $this->typeLabels = array( + 'S' => $GLOBALS['LANG']->getLL('type_system'), + 'G' => $GLOBALS['LANG']->getLL('type_global'), + 'L' => $GLOBALS['LANG']->getLL('type_local'), + ); + $this->typeDescr = array( + 'S' => $GLOBALS['LANG']->getLL('descr_system'), + 'G' => $GLOBALS['LANG']->getLL('descr_global'), + 'L' => $GLOBALS['LANG']->getLL('descr_local'), + ); + + // Setting paths of install scopes: + $this->typePaths = Array ( + 'S' => TYPO3_mainDir.'sysext/', + 'G' => TYPO3_mainDir.'ext/', + 'L' => 'typo3conf/ext/' + ); + $this->typeBackPaths = Array ( + 'S' => '../../../', + 'G' => '../../../', + 'L' => '../../../../'.TYPO3_mainDir + ); + + $this->privacyNotice = $GLOBALS['LANG']->getLL('privacy_notice'); + $securityMessage = $GLOBALS['LANG']->getLL('security_warning_extensions') . + '

' . sprintf($GLOBALS['LANG']->getLL('security_descr'), + '', '' + ); + $flashMessage = t3lib_div::makeInstance( + 't3lib_FlashMessage', + $securityMessage, + $GLOBALS['LANG']->getLL('security_header'), + t3lib_FlashMessage::INFO + ); + $this->securityHint = $flashMessage->render(); + + $this->excludeForPackaging = $GLOBALS['TYPO3_CONF_VARS']['EXT']['excludeForPackaging']; + + // Setting module configuration: + $this->MCONF = $GLOBALS['MCONF']; + + // Setting GPvars: + $this->CMD = is_array(t3lib_div::_GP('CMD')) ? t3lib_div::_GP('CMD') : array(); + $this->lookUpStr = trim(t3lib_div::_GP('lookUp')); + $this->listRemote = t3lib_div::_GP('ter_connect'); + $this->listRemote_search = trim(t3lib_div::_GP('ter_search')); + + + // Configure menu + $this->menuConfig(); + + // Setting internal static: + if ($TYPO3_CONF_VARS['EXT']['allowSystemInstall']) $this->systemInstall = 1; + $this->requiredExt = t3lib_div::trimExplode(',',$TYPO3_CONF_VARS['EXT']['requiredExt'],1); + + + // Initialize helper object + $this->terConnection = t3lib_div::makeInstance('SC_mod_tools_em_terconnection'); + $this->terConnection->emObj = $this; + $this->terConnection->wsdlURL = $TYPO3_CONF_VARS['EXT']['em_wsdlURL']; + $this->xmlhandler = t3lib_div::makeInstance('SC_mod_tools_em_xmlhandler'); + $this->xmlhandler->emObj = $this; + $this->xmlhandler->useObsolete = $this->MOD_SETTINGS['display_obsolete']; + + // Initialize Document Template object: + $this->doc = t3lib_div::makeInstance('template'); + $this->doc->backPath = $BACK_PATH; + $this->doc->setModuleTemplate('templates/em_index.html'); + // the id is needed for getting same styles TODO: general table styles + $this->doc->bodyTagId = 'typo3-mod-tools-em-index-php'; + // JavaScript + $this->doc->JScode = $this->doc->wrapScriptTags(' + script_ended = 0; + function jumpToUrl(URL) { // + window.location.href = URL; + } + '); + + // Reload left frame menu + if ($this->CMD['refreshMenu']) { + $this->doc->JScode .= $this->doc->wrapScriptTags(' + if(top.refreshMenu) { + top.refreshMenu(); + } else { + top.TYPO3ModuleMenu.refreshMenu(); + } + '); + } + + + // Descriptions: + $this->descrTable = '_MOD_'.$this->MCONF['name']; + if ($BE_USER->uc['edit_showFieldHelp']) { + $LANG->loadSingleTableDescription($this->descrTable); + } + + // Setting username/password etc. for upload-user: + $this->fe_user['username'] = $this->MOD_SETTINGS['fe_u']; + $this->fe_user['password'] = $this->MOD_SETTINGS['fe_p']; + parent::init(); + $this->handleExternalFunctionValue('singleDetails'); + } + + /** + * This function is a copy of the same function in t3lib_SCbase with one modification: + * In contrast to t3lib_SCbase::handleExternalFunctionValue() this function merges the $this->extClassConf array + * instead of overwriting it. That was necessary for including the Kickstarter as a submodule into the 'singleDetails' + * selectorbox as well as in the main 'function' selectorbox. + * + * @param string Mod-setting array key + * @param string Mod setting value, overriding the one in the key + * @return void + * @see t3lib_SCbase::handleExternalFunctionValue() + */ + function handleExternalFunctionValue($MM_key='function', $MS_value=NULL) { + $MS_value = is_null($MS_value) ? $this->MOD_SETTINGS[$MM_key] : $MS_value; + $externalItems = $this->getExternalItemConfig($this->MCONF['name'],$MM_key,$MS_value); + if (is_array($externalItems)) $this->extClassConf = array_merge($externalItems,is_array($this->extClassConf)?$this->extClassConf:array()); + if (is_array($this->extClassConf) && $this->extClassConf['path']) { + $this->include_once[]=$this->extClassConf['path']; + } + } + + /** + * Configuration of which mod-menu items can be used + * + * @return void + */ + function menuConfig() { + global $BE_USER, $TYPO3_CONF_VARS; + + // MENU-ITEMS: + $this->MOD_MENU = array( + 'function' => array( + 0 => $GLOBALS['LANG']->getLL('menu_loaded_extensions'), + 1 => $GLOBALS['LANG']->getLL('menu_install_extensions'), + 2 => $GLOBALS['LANG']->getLL('menu_import_extensions'), + 4 => $GLOBALS['LANG']->getLL('menu_translation_handling'), + 3 => $GLOBALS['LANG']->getLL('menu_settings'), + 5 => $GLOBALS['LANG']->getLL('menu_extension_updates'), + ), + 'listOrder' => array( + 'cat' => $GLOBALS['LANG']->getLL('list_order_category'), + 'author_company' => $GLOBALS['LANG']->getLL('list_order_author'), + 'state' => $GLOBALS['LANG']->getLL('list_order_state'), + 'type' => $GLOBALS['LANG']->getLL('list_order_type'), + ), + 'display_details' => array( + 1 => $GLOBALS['LANG']->getLL('show_details'), + 0 => $GLOBALS['LANG']->getLL('show_description'), + 2 => $GLOBALS['LANG']->getLL('show_more_details'), + + 3 => $GLOBALS['LANG']->getLL('show_technical'), + 4 => $GLOBALS['LANG']->getLL('show_validating'), + 5 => $GLOBALS['LANG']->getLL('show_changed'), + ), + 'display_shy' => '', + 'display_own' => '', + 'display_obsolete' => '', + 'display_installed' => '', + 'display_files' => '', + + + 'singleDetails' => array( + 'info' => $GLOBALS['LANG']->getLL('details_info'), + 'edit' => $GLOBALS['LANG']->getLL('details_edit'), + 'backup' => $GLOBALS['LANG']->getLL('details_backup_delete'), + 'dump' => $GLOBALS['LANG']->getLL('details_dump_db'), + 'upload' => $GLOBALS['LANG']->getLL('details_upload'), + 'updateModule' => $GLOBALS['LANG']->getLL('details_update'), + ), + 'fe_u' => '', + 'fe_p' => '', + + 'mirrorListURL' => '', + 'rep_url' => '', + 'extMirrors' => '', + 'selectedMirror' => '', + + 'selectedLanguages' => '' + ); + + $this->MOD_MENU['singleDetails'] = $this->mergeExternalItems($this->MCONF['name'],'singleDetails',$this->MOD_MENU['singleDetails']); + + // page/be_user TSconfig settings and blinding of menu-items + if (!$BE_USER->getTSConfigVal('mod.'.$this->MCONF['name'].'.allowTVlisting')) { + unset($this->MOD_MENU['display_details'][3]); + unset($this->MOD_MENU['display_details'][4]); + unset($this->MOD_MENU['display_details'][5]); + } + + // CLEANSE SETTINGS + $this->MOD_SETTINGS = t3lib_BEfunc::getModuleData($this->MOD_MENU, t3lib_div::_GP('SET'), $this->MCONF['name']); + + if ($this->MOD_SETTINGS['function']==2) { + // If listing from online repository, certain items are removed though: + unset($this->MOD_MENU['listOrder']['type']); + unset($this->MOD_MENU['display_details'][2]); + unset($this->MOD_MENU['display_details'][3]); + unset($this->MOD_MENU['display_details'][4]); + unset($this->MOD_MENU['display_details'][5]); + $this->MOD_SETTINGS = t3lib_BEfunc::getModuleData($this->MOD_MENU, t3lib_div::_GP('SET'), $this->MCONF['name']); + } + parent::menuConfig(); + } + + /** + * Main function for Extension Manager module. + * + * @return void + */ + function main() { + global $BE_USER,$LANG,$TYPO3_CONF_VARS; + + if (empty($this->MOD_SETTINGS['mirrorListURL'])) $this->MOD_SETTINGS['mirrorListURL'] = $TYPO3_CONF_VARS['EXT']['em_mirrorListURL']; + + // Starting page: + $this->content.=$this->doc->header($GLOBALS['LANG']->getLL('header')); + $this->content.=$this->doc->spacer(5); + + // Command given which is executed regardless of main menu setting: + if ($this->CMD['showExt']) { // Show details for a single extension + $this->showExtDetails($this->CMD['showExt']); + } elseif ($this->CMD['requestInstallExtensions']) { // Show details for a single extension + $this->requestInstallExtensions($this->CMD['requestInstallExtensions']); + } elseif ($this->CMD['importExt'] || $this->CMD['uploadExt']) { // Imports an extension from online rep. + $err = $this->importExtFromRep($this->CMD['importExt'],$this->CMD['extVersion'],$this->CMD['loc'],$this->CMD['uploadExt']); + if ($err) { + $this->content.=$this->doc->section('',$GLOBALS['TBE_TEMPLATE']->rfw($err)); + } + if(!$err && $this->CMD['importExt']) { + $this->installTranslationsForExtension($this->CMD['importExt'], $this->getMirrorURL()); + } + } elseif ($this->CMD['importExtInfo']) { // Gets detailed information of an extension from online rep. + $this->importExtInfo($this->CMD['importExtInfo'],$this->CMD['extVersion']); + } else { // No command - we show what the menu setting tells us: + if (t3lib_div::inList('0,1,2',$this->MOD_SETTINGS['function'])) { + $menu .= ' ' . $GLOBALS['LANG']->getLL('group_by') . ' ' . t3lib_BEfunc::getFuncMenu(0, 'SET[listOrder]', $this->MOD_SETTINGS['listOrder'], $this->MOD_MENU['listOrder']) . + '  ' . $GLOBALS['LANG']->getLL('show') . ' ' . t3lib_BEfunc::getFuncMenu(0, 'SET[display_details]', $this->MOD_SETTINGS['display_details'], $this->MOD_MENU['display_details']) . '
'; + } + if (t3lib_div::inList('0,1,5',$this->MOD_SETTINGS['function'])) { + $menu.='  ' . t3lib_BEfunc::getFuncCheck(0, 'SET[display_shy]', $this->MOD_SETTINGS['display_shy'], '', '', 'id="checkDisplayShy"'); + } + if (t3lib_div::inList('2',$this->MOD_SETTINGS['function']) && strlen($this->fe_user['username'])) { + $menu.='  ' . t3lib_BEfunc::getFuncCheck(0, 'SET[display_own]', $this->MOD_SETTINGS['display_own'], '', '', 'id="checkDisplayOwn"'); + } + if (t3lib_div::inList('0,1,2',$this->MOD_SETTINGS['function'])) { + $menu.='    ' . t3lib_BEfunc::getFuncCheck(0, 'SET[display_obsolete]', $this->MOD_SETTINGS['display_obsolete'], '', '', 'id="checkDisplayObsolete"'); + } + + $this->content.=$this->doc->section('','
' . ($menu ? $menu : ' ') . '
'); + $this->content.=$this->doc->spacer(10); + + switch((string)$this->MOD_SETTINGS['function']) { + case '0': + // Lists loaded (installed) extensions + $this->extensionList_loaded(); + break; + case '1': + // Lists the installed (available) extensions + $this->extensionList_installed(); + break; + case '2': + // Lists the extensions available from online rep. + $this->extensionList_import(); + break; + case '3': + // Shows the settings screen + $this->alterSettings(); + break; + case '4': + // Allows to set the translation preferences and check the status + $this->translationHandling(); + break; + case '5': + // Shows a list of extensions with updates in TER + $this->checkForUpdates(); + break; + default: + $this->extObjContent(); + break; + } + } + + // closing any form? + $formTags = substr_count($this->content, 'content, ' 0) { + $this->content .= ''; + } + + // Setting up the buttons and markers for docheader + $docHeaderButtons = $this->getButtons(); + $markers = array( + 'CSH' => $docHeaderButtons['csh'], + 'FUNC_MENU' => $this->getFuncMenu(), + 'CONTENT' => $this->content + ); + + // Build the for the module + $this->content = $this->doc->startPage('Extension Manager'); + $this->content.= $this->doc->moduleBody($this->pageinfo, $docHeaderButtons, $markers); + $this->content.= $this->doc->endPage(); + $this->content = $this->doc->insertStylesAndJS($this->content); + } + + /** + * Print module content. Called as last thing in the global scope. + * + * @return void + */ + function printContent() { + if ($this->doPrintContent) { + echo $this->content; + } + } + + /** + * Create the function menu + * + * @return string HTML of the function menu + */ + protected function getFuncMenu() { + $funcMenu = ''; + if(!$this->CMD['showExt'] && !$this->CMD['requestInstallExtensions'] && !$this->CMD['importExt'] && !$this->CMD['uploadExt'] && !$this->CMD['importExtInfo']) { + $funcMenu = t3lib_BEfunc::getFuncMenu(0, 'SET[function]', $this->MOD_SETTINGS['function'], $this->MOD_MENU['function']); + } elseif($this->CMD['showExt'] && (!$this->CMD['standAlone'] && !t3lib_div::_GP('standAlone'))) { + $funcMenu = t3lib_BEfunc::getFuncMenu(0, 'SET[singleDetails]', $this->MOD_SETTINGS['singleDetails'], $this->MOD_MENU['singleDetails'], '', '&CMD[showExt]=' . $this->CMD['showExt']); + } + return $funcMenu; + } + + /** + * Create the panel of buttons for submitting the form or otherwise perform operations. + * + * @return array all available buttons as an assoc. array + */ + protected function getButtons() { + + $buttons = array( + 'csh' => '', + 'back' => '', + 'shortcut' => '' + ); + // CSH + //$buttons['csh'] = t3lib_BEfunc::cshItem('_MOD_web_func', '', $GLOBALS['BACK_PATH']); + + // Shortcut + if ($GLOBALS['BE_USER']->mayMakeShortcut()) { + $buttons['shortcut'] = $this->doc->makeShortcutIcon('CMD','function',$this->MCONF['name']); + } + // Back + if(($this->CMD['showExt'] && (!$this->CMD['standAlone'] && !t3lib_div::_GP('standAlone'))) || ($this->CMD['importExt'] || $this->CMD['uploadExt'] && (!$this->CMD['standAlone'])) || $this->CMD['importExtInfo']) { + $buttons['back'] = '' . + t3lib_iconWorks::getSpriteIcon('actions-view-go-back') . + ''; + } + + return $buttons; + } + + + + + + + + + /********************************* + * + * Function Menu Applications + * + *********************************/ + + /** + * Listing of loaded (installed) extensions + * + * @return void + */ + function extensionList_loaded() { + global $TYPO3_LOADED_EXT; + + list($list,$cat) = $this->getInstalledExtensions(); + + // Loaded extensions + $content = ''; + $lines = array(); + + // Available extensions + if (is_array($cat[$this->MOD_SETTINGS['listOrder']])) { + $content=''; + $lines=array(); + $lines[] = $this->extensionListRowHeader(' class="t3-row-header"',array('')); + + foreach($cat[$this->MOD_SETTINGS['listOrder']] as $catName => $extEkeys) { + natcasesort($extEkeys); + $extensions = array(); + foreach ($extEkeys as $extKey => $value) { + if (array_key_exists($extKey,$TYPO3_LOADED_EXT) && ($this->MOD_SETTINGS['display_shy'] || !$list[$extKey]['EM_CONF']['shy']) && $this->searchExtension($extKey,$list[$extKey])) { + if (in_array($extKey, $this->requiredExt)) { + $loadUnloadLink = '' . $GLOBALS['TBE_TEMPLATE']->rfw($GLOBALS['LANG']->getLL('extension_required_short')) . ''; + } else { + $loadUnloadLink = ''.$this->removeButton().''; + } + + $extensions[] = $this->extensionListRow($extKey,$list[$extKey],array(''.$loadUnloadLink.'')); + } + } + if(count($extensions)) { + $lines[]='
'; + $lines[]='' . t3lib_iconWorks::getSpriteIcon('apps-filetree-folder-default') . ''.htmlspecialchars($this->listOrderTitle($this->MOD_SETTINGS['listOrder'],$catName)).''; + $lines[] = implode(LF,$extensions); + } + } + } + + $content.= t3lib_BEfunc::cshItem('_MOD_tools_em', 'loaded', $GLOBALS['BACK_PATH'],''); + $content.= '
'; + $content.= '

'; + + $content.= '
+ + + '.implode('',$lines).'
'; + + $this->content.=$this->doc->section($GLOBALS['LANG']->getLL('loaded_exts'),$content,0,1); + } + + /** + * Listing of available (installed) extensions + * + * @return void + */ + function extensionList_installed() { + global $TYPO3_LOADED_EXT; + + list($list,$cat)=$this->getInstalledExtensions(); + + // Available extensions + if (is_array($cat[$this->MOD_SETTINGS['listOrder']])) { + $content=''; + $lines=array(); + $lines[]=$this->extensionListRowHeader(' class="t3-row-header"',array('')); + + $allKeys=array(); + foreach($cat[$this->MOD_SETTINGS['listOrder']] as $catName => $extEkeys) { + if(!$this->MOD_SETTINGS['display_obsolete'] && $catName=='obsolete') continue; + + $allKeys[]=''; + $allKeys[]='TYPE: '.$catName; + + natcasesort($extEkeys); + $extensions = array(); + foreach ($extEkeys as $extKey => $value) { + $allKeys[]=$extKey; + if ((!$list[$extKey]['EM_CONF']['shy'] || $this->MOD_SETTINGS['display_shy']) && + ($list[$extKey]['EM_CONF']['state']!='obsolete' || $this->MOD_SETTINGS['display_obsolete']) + && $this->searchExtension($extKey,$list[$extKey])) { + $loadUnloadLink = t3lib_extMgm::isLoaded($extKey)? + ''.$this->removeButton().'': + ''.$this->installButton().''; + if (in_array($extKey,$this->requiredExt)) { + $loadUnloadLink = '' . $GLOBALS['TBE_TEMPLATE']->rfw($GLOBALS['LANG']->getLL('extension_required_short')) . ''; + } + $theRowClass = t3lib_extMgm::isLoaded($extKey)? 'em-listbg1' : 'em-listbg2'; + $extensions[]=$this->extensionListRow($extKey,$list[$extKey],array(''.$loadUnloadLink.''),$theRowClass); + } + } + if(count($extensions)) { + $lines[]='
'; + $lines[]='' . t3lib_iconWorks::getSpriteIcon('apps-filetree-folder-default') . ''. htmlspecialchars($this->listOrderTitle($this->MOD_SETTINGS['listOrder'],$catName)).''; + $lines[] = implode(LF,$extensions); + } + } + + $content.=' + + + + +'; + + $content.= t3lib_BEfunc::cshItem('_MOD_tools_em', 'avail', $GLOBALS['BACK_PATH'], '|
'); + $content.= sprintf($GLOBALS['LANG']->getLL('how_to_install'), $this->installButton()) . '
' . + sprintf($GLOBALS['LANG']->getLL('how_to_uninstall'), $this->removeButton()). '

'; + $content .= '
'; + $content .= '


'; + $content.= $this->securityHint.'

'; + + $content.= ''.implode('',$lines).'
'; + + $this->content.=$this->doc->section(sprintf($GLOBALS['LANG']->getLL('available_extensions'), $this->MOD_MENU['listOrder'][$this->MOD_SETTINGS['listOrder']]),$content,0,1); + } + } + + /** + * Listing remote extensions from online repository + * + * @return void + */ + function extensionList_import() { + global $TYPO3_LOADED_EXT; + $content=''; + + // Listing from online repository: + if ($this->listRemote) { + list($inst_list,) = $this->getInstalledExtensions(); + $this->inst_keys = array_flip(array_keys($inst_list)); + + $this->detailCols[1]+=6; + + // see if we have an extensionlist at all + $this->extensionCount = $this->xmlhandler->countExtensions(); + if (!$this->extensionCount) { + $content .= $this->fetchMetaData('extensions'); + } + + if($this->MOD_SETTINGS['listOrder']=='author_company') { + $this->listingLimit = $this->listingLimitAuthor; + } + + $this->pointer = intval(t3lib_div::_GP('pointer')); + $offset = $this->listingLimit*$this->pointer; + + if($this->MOD_SETTINGS['display_own'] && strlen($this->fe_user['username'])) { + $this->xmlhandler->searchExtensionsXML($this->listRemote_search, $this->fe_user['username'], $this->MOD_SETTINGS['listOrder'], TRUE); + } else { + $this->xmlhandler->searchExtensionsXML($this->listRemote_search, '', $this->MOD_SETTINGS['listOrder'], TRUE, FALSE, $offset, $this->listingLimit); + } + if (count($this->xmlhandler->extensionsXML)) { + list($list,$cat) = $this->prepareImportExtList(true); + + // Available extensions + if (is_array($cat[$this->MOD_SETTINGS['listOrder']])) { + $lines=array(); + $lines[]=$this->extensionListRowHeader(' class="t3-row-header"',array(''),1); + + foreach($cat[$this->MOD_SETTINGS['listOrder']] as $catName => $extEkeys) { + if (count($extEkeys)) { + $lines[]='
'; + $lines[]='' . t3lib_iconWorks::getSpriteIcon('apps-filetree-folder-default') . ''.htmlspecialchars($this->listOrderTitle($this->MOD_SETTINGS['listOrder'],$catName)).''; + + natcasesort($extEkeys); + foreach ($extEkeys as $extKey => $value) { + $version = array_keys($list[$extKey]['versions']); + $version = end($version); + $ext = $list[$extKey]['versions'][$version]; + $ext['downloadcounter_all'] = $list[$extKey]['downloadcounter']; + $ext['_ICON'] = $list[$extKey]['_ICON']; + $loadUnloadLink=''; + if ($inst_list[$extKey]['type']!='S' && (!isset($inst_list[$extKey]) || $this->versionDifference($version,$inst_list[$extKey]['EM_CONF']['version'],$this->versionDiffFactor))) { + if (isset($inst_list[$extKey])) { + // update + if ($inst_list[$extKey]['EM_CONF']['state'] != 'excludeFromUpdates') { + $loc= ($inst_list[$extKey]['type']=='G'?'G':'L'); + $aUrl = 'index.php?CMD[importExt]='.$extKey.'&CMD[extVersion]='.$version.'&CMD[loc]='.$loc; + $loadUnloadLink .= '' . + t3lib_iconWorks::getSpriteIcon('actions-system-extension-update') . + ''; + } else { + // extension is marked as "excludeFromUpdates" + $loadUnloadLink .= t3lib_iconWorks::getSpriteIcon('status-dialog-warning', $GLOBALS['LANG']->getLL('excluded_from_updates') ); + } + } else { + // import + $aUrl = 'index.php?CMD[importExt]='.$extKey.'&CMD[extVersion]='.$version.'&CMD[loc]=L'; + $loadUnloadLink .= '' . t3lib_iconWorks::getSpriteIcon('actions-system-extension-import') . ''; + } + } else { + $loadUnloadLink = ' '; + } + + if (isset($inst_list[$extKey])) { + $theRowClass = t3lib_extMgm::isLoaded($extKey) ? 'em-listbg1' : 'em-listbg2'; + } else { + $theRowClass = 'em-listbg3'; + } + + $lines[]=$this->extensionListRow($extKey,$ext,array(''.$loadUnloadLink.''),$theRowClass,$inst_list,1,'index.php?CMD[importExtInfo]='.rawurlencode($extKey)); + unset($list[$extKey]); + } + } + } + unset($list); + + // CSH: + $content .= t3lib_BEfunc::cshItem('_MOD_tools_em', 'import_ter', $GLOBALS['BACK_PATH'], '|
'); + $onsubmit = "window.location.href='index.php?ter_connect=1&ter_search='+escape(this.elements['lookUp'].value);return false;"; + $content .= '

+


'; + + $content .= $this->browseLinks(); + + $content.= ' + + + '.implode(LF,$lines).'
'; + $content .= '
'.$this->browseLinks(); + $content.= '

'.$this->securityHint; + $content .= '

' . $GLOBALS['LANG']->getLL('privacy_notice_header') . + '
' . $this->privacyNotice; + + $this->content .= $this->doc->section($GLOBALS['LANG']->getLL('extensions_repository_group_by') . ' ' . + $this->MOD_MENU['listOrder'][$this->MOD_SETTINGS['listOrder']], $content, 0, 1); + + // Plugins which are NOT uploaded to repository but present on this server. + $content=''; + $lines=array(); + if (count($this->inst_keys)) { + foreach ($this->inst_keys as $extKey => $value) { + $this->xmlhandler->searchExtensionsXMLExact($extKey, '', '', true); + if((strlen($this->listRemote_search) && !stristr($extKey,$this->listRemote_search)) || isset($this->xmlhandler->extensionsXML[$extKey])) continue; + + $loadUnloadLink = t3lib_extMgm::isLoaded($extKey)? + ''.$this->removeButton().'': + ''.$this->installButton().''; + if (in_array($extKey,$this->requiredExt)) { + $loadUnloadLink = '' .$GLOBALS['TBE_TEMPLATE']->rfw($GLOBALS['LANG']->getLL('extension_required_short')) . ''; + } + $lines[]=$this->extensionListRow($extKey,$inst_list[$extKey],array(''.$loadUnloadLink.''),t3lib_extMgm::isLoaded($extKey)?'em-listbg1':'em-listbg2'); + } + } + if(count($lines)) { + $content .= $GLOBALS['LANG']->getLL('list_of_local_extensions') . + '
' . $GLOBALS['LANG']->getLL('might_be_user_defined') . '

'; + $content.= ''. + $this->extensionListRowHeader(' class="t3-row-header"',array('')). + implode('',$lines).'
'; + $this->content.=$this->doc->spacer(20); + $this->content.=$this->doc->section($GLOBALS['LANG']->getLL('only_on_this_server'), $content, 0, 1); + } + } + } else { + $content .= t3lib_BEfunc::cshItem('_MOD_tools_em', 'import_ter', $GLOBALS['BACK_PATH'], '|
'); + $onsubmit = "window.location.href='index.php?ter_connect=1&ter_search='+escape(this.elements['lookUp'].value);return false;"; + $content .= '

+


'; + + $content .= '

' . $GLOBALS['LANG']->getLL('no_matching_extensions') . '

'; + + $content .= '

' . $GLOBALS['LANG']->getLL('privacy_notice_header') . + '
' . $this->privacyNotice; + $this->content.=$this->doc->section($GLOBALS['LANG']->getLL('extensions_repository_group_by') . ' ' . + $this->MOD_MENU['listOrder'][$this->MOD_SETTINGS['listOrder']], $content, 0, 1); + } + } else { + // CSH + $content .= t3lib_BEfunc::cshItem('_MOD_tools_em', 'import', $GLOBALS['BACK_PATH'], '|
'); + + $onsubmit = "window.location.href='index.php?ter_connect=1&ter_search='+escape(this.elements['lookUp'].value);return false;"; + $content .= '

+

'; + + if ($this->CMD['fetchMetaData']) { // fetches mirror/extension data from online rep. + $content .= $this->fetchMetaData($this->CMD['fetchMetaData']); + } else { + $onCLick = "window.location.href='index.php?CMD[fetchMetaData]=extensions';return false;"; + $content .= $GLOBALS['LANG']->getLL('connect_to_ter') . '
+ '; + if (is_file(PATH_site.'typo3temp/extensions.xml.gz')) { + $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy']; + $timeFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm']; + $content .= ' ' . sprintf($GLOBALS['LANG']->getLL('ext_list_last_updated') . ' ', + date( + $dateFormat . ', ' . $timeFormat, + filemtime(PATH_site . 'typo3temp/extensions.xml.gz') + ) + ); + } + } + $content.= '


'.$this->securityHint; + $content .= '

' . $GLOBALS['LANG']->getLL('privacy_notice_header') . + '
' . $this->privacyNotice; + + $this->content.=$this->doc->section($GLOBALS['LANG']->getLL('in_repository'), $content, 0, 1); + } + + // Upload: + if ($this->importAtAll()) { + $content= '
+
+
' . + $GLOBALS['LANG']->getLL('upload_to_location') . '
+
+
+

+ '; + } else $content=$this->noImportMsg(); + + $this->content.=$this->doc->spacer(20); + $this->content .= $this->doc->section($GLOBALS['LANG']->getLL('upload_ext_directly'), $content, 0, 1); + } + + /** + * Generates a link to the next page of extensions + * + * @return void + */ + function browseLinks() { + $content = ''; + if ($this->pointer) { + $content .= ' ' . + $GLOBALS['LANG']->getLL('previous_page') . ''; + } + if ($content) $content .= '   '; + if (intval($this->xmlhandler->matchingCount/$this->listingLimit)>$this->pointer) { + $content .= ' ' . + $GLOBALS['LANG']->getLL('next_page') . ''; + } + $upper = (($this->pointer+1)*$this->listingLimit); + if ($upper>$this->xmlhandler->matchingCount) { + $upper = $this->xmlhandler->matchingCount; + } + if ($content) $content .= '

' . + sprintf($GLOBALS['LANG']->getLL('showing_extensions_from_to'), + '' . ($this->pointer*$this->listingLimit+1) . '', + '' . $upper . '' + ); + if ($content) $content .= '

'; + return $content; + } + + /** + * Allows changing of settings + * + * @return void + */ + function alterSettings() { + + // Prepare the HTML output: + $content.= ' + ' . t3lib_BEfunc::cshItem('_MOD_tools_em', 'settings', $GLOBALS['BACK_PATH'], '|
') . ' +
+
' . $GLOBALS['LANG']->getLL('user_settings') . ' + + + + + + + + + +
+ ' . $GLOBALS['LANG']->getLL('notice') . ' ' . + $GLOBALS['LANG']->getLL('repository_password_info') . ' +
+
+
+
' . $GLOBALS['LANG']->getLL('mirror_selection') . ' + + + + + +
+
+
+

' . $GLOBALS['LANG']->getLL('mirror_select') . '

+
' . $GLOBALS['LANG']->getLL('mirror_list') . ''; + if(!empty($this->MOD_SETTINGS['mirrorListURL'])) { + if ($this->CMD['fetchMetaData']) { // fetches mirror/extension data from online rep. + $content .= $this->fetchMetaData($this->CMD['fetchMetaData']); + } else { + $content .= '' . $GLOBALS['LANG']->getLL('mirror_list_reload') . ''; + } + } + $content .= '
+ + + + + + + + + '; + + if (!strlen($this->MOD_SETTINGS['extMirrors'])) $this->fetchMetaData('mirrors'); + $extMirrors = unserialize($this->MOD_SETTINGS['extMirrors']); + $extMirrors[''] = array('title'=>$GLOBALS['LANG']->getLL('mirror_use_random')); + ksort($extMirrors); + if(is_array($extMirrors)) { + foreach($extMirrors as $k => $v) { + if(isset($v['sponsor'])) { + $sponsor = ''.htmlspecialchars($v['sponsor']['name']).''; + } + $selected = ($this->MOD_SETTINGS['selectedMirror']==$k) ? 'checked="checked"' : ''; + $content.=' + '; + } + } + $content.= ' +
' . $GLOBALS['LANG']->getLL('mirror_use') . '' . $GLOBALS['LANG']->getLL('mirror_name') . '' . $GLOBALS['LANG']->getLL('mirror_url') . '' . $GLOBALS['LANG']->getLL('mirror_country') . '' . $GLOBALS['LANG']->getLL('mirror_sponsored_by') . '
'.htmlspecialchars($v['host'].$v['path']).''.$v['country'].''.$sponsor.'
+
+
+
+ + + + + +
+ + ' . $GLOBALS['LANG']->getLL('repository_url_hint') . '
+
+
+ +
+ '; + + $this->content .= $this->doc->section($GLOBALS['LANG']->getLL('repository_settings'), $content, 0, 1); + } + + /** + * Allows to set the translation preferences and check the status + * + * @return void + */ + function translationHandling() { + global $LANG, $TYPO3_LOADED_EXT; + $LANG->includeLLFile('EXT:setup/mod/locallang.xml'); + + //prepare docheader + $docHeaderButtons = $this->getButtons(); + $markers = array( + 'CSH' => $docHeaderButtons['csh'], + 'FUNC_MENU' => $this->getFuncMenu(), + ); + + + $incoming = t3lib_div::_POST('SET'); + if(isset($incoming['selectedLanguages']) && is_array($incoming['selectedLanguages'])) { + t3lib_BEfunc::getModuleData($this->MOD_MENU, array('selectedLanguages' => serialize($incoming['selectedLanguages'])), $this->MCONF['name'], '', 'selectedLanguages'); + $this->MOD_SETTINGS['selectedLanguages'] = serialize($incoming['selectedLanguages']); + } + + $selectedLanguages = unserialize($this->MOD_SETTINGS['selectedLanguages']); + if(count($selectedLanguages)==1 && empty($selectedLanguages[0])) $selectedLanguages = array(); + $theLanguages = t3lib_div::trimExplode('|',TYPO3_languages); + foreach($theLanguages as $val) { + if ($val!='default') { + $localLabel = ' - ['.htmlspecialchars($GLOBALS['LOCAL_LANG']['default']['lang_'.$val]).']'; + $selected = (is_array($selectedLanguages) && in_array($val, $selectedLanguages)) ? ' selected="selected"' : ''; + $opt[$GLOBALS['LANG']->getLL('lang_' . $val, 1) . '--' . $val] = ' + '; + } + } + ksort($opt); + + // Prepare the HTML output: + $content.= ' + ' . t3lib_BEfunc::cshItem('_MOD_tools_em', 'translation', $GLOBALS['BACK_PATH'], '|
') . ' +
+
' . $GLOBALS['LANG']->getLL('translation_settings') . ' + + + + + +
' . $GLOBALS['LANG']->getLL('languages_to_fetch') . ' + +
+
+

' . $GLOBALS['LANG']->getLL('translation_info') . '
+
' . $GLOBALS['LANG']->getLL('translation_loaded_exts') . '

+
+
+ +
+
'; + + $this->content .= $this->doc->section($GLOBALS['LANG']->getLL('translation_settings'), $content, 0, 1); + + if(count($selectedLanguages)>0) { + $mirrorURL = $this->getMirrorURL(); + $content = ' '; + + // as this page loads dynamically, quit output buffering caused by ob_gzhandler + t3lib_div::cleanOutputBuffers(); + + if(t3lib_div::_GET('l10n') == 'check') { + $loadedExtensions = array_keys($TYPO3_LOADED_EXT); + $loadedExtensions = array_diff($loadedExtensions,array('_CACHEFILE')); + + // Override content output - we now do that ourselves: + $this->content .= $this->doc->section($GLOBALS['LANG']->getLL('translation_status'), $content, 0, 1); + // Setting up the buttons and markers for docheader + $content = $this->doc->startPage('Extension Manager'); + $content.= $this->doc->moduleBody($this->pageinfo, $docHeaderButtons, $markers); + $contentParts=explode('###CONTENT###',$content); + + echo $contentParts[0].$this->content; + + $this->doPrintContent = FALSE; + flush(); + + echo ' +
+
+

+ ' . $GLOBALS['LANG']->getLL('translation_check_status') . ' +

+
+
+
 
+
 
+
+
+

' . $GLOBALS['LANG']->getLL('translation_table_check') . '


+ + + '; + + foreach($selectedLanguages as $lang) { + echo (''); + } + echo (''); + + $counter = 1; + foreach($loadedExtensions as $extKey) { + + $percentDone = intval (($counter / count($loadedExtensions)) * 100); + echo (' + + '); + + flush(); + $translationStatusArr = $this->terConnection->fetchTranslationStatus($extKey,$mirrorURL); + + echo (''); + foreach($selectedLanguages as $lang) { + // remote unknown -> no l10n available + if(!isset($translationStatusArr[$lang])) { + echo (''); + continue; + } + // determine local md5 from zip + if(is_file(PATH_site.'typo3temp/'.$extKey.'-l10n-'.$lang.'.zip')) { + $localmd5 = md5_file(PATH_site.'typo3temp/'.$extKey.'-l10n-'.$lang.'.zip'); + } else { + echo (''); + continue; + } + // local!=remote -> needs update + if($localmd5 != $translationStatusArr[$lang]['md5']) { + echo (''); + continue; + } + echo (''); + } + echo (''); + + $counter ++; + } + echo '
' . $GLOBALS['LANG']->getLL('translation_extension_key') . ''.$LANG->getLL('lang_'.$lang,1).'
'.$extKey.'' . + $GLOBALS['LANG']->getLL('translation_n_a') . '' . $GLOBALS['LANG']->getLL('translation_status_unknown') . + '' . $GLOBALS['LANG']->getLL('translation_status_update') . + '' . $GLOBALS['LANG']->getLL('translation_status_ok') . + '
+ + '; + echo $contentParts[1] . $this->doc->endPage(); + exit; + + } elseif(t3lib_div::_GET('l10n') == 'update') { + $loadedExtensions = array_keys($TYPO3_LOADED_EXT); + $loadedExtensions = array_diff($loadedExtensions,array('_CACHEFILE')); + + // Override content output - we now do that ourselves: + $this->content .= $this->doc->section($GLOBALS['LANG']->getLL('translation_status'), $content, 0, 1); + // Setting up the buttons and markers for docheader + $content = $this->doc->startPage('Extension Manager'); + $content.= $this->doc->moduleBody($this->pageinfo, $docHeaderButtons, $markers); + $contentParts=explode('###CONTENT###',$content); + + echo $contentParts[0].$this->content; + + $this->doPrintContent = FALSE; + flush(); + + echo (' +
+
+

+ ' . $GLOBALS['LANG']->getLL('translation_update_status') . ' +

+
+
+
 
+
 
+
+
+

' . $GLOBALS['LANG']->getLL('translation_table_update') . '
+ ' . $GLOBALS['LANG']->getLL('translation_full_check_update') . '


+ + + '); + + foreach($selectedLanguages as $lang) { + echo ''; + } + echo ''; + + $counter = 1; + foreach($loadedExtensions as $extKey) { + $percentDone = intval (($counter / count($loadedExtensions)) * 100); + echo (' + + '); + + flush(); + $translationStatusArr = $this->terConnection->fetchTranslationStatus($extKey,$mirrorURL); + + echo (''); + if(is_array($translationStatusArr)) { + foreach($selectedLanguages as $lang) { + // remote unknown -> no l10n available + if(!isset($translationStatusArr[$lang])) { + echo (''); + continue; + } + // determine local md5 from zip + if(is_file(PATH_site.'typo3temp/'.$extKey.'-l10n-'.$lang.'.zip')) { + $localmd5 = md5_file(PATH_site.'typo3temp/'.$extKey.'-l10n-'.$lang.'.zip'); + } else { + $localmd5 = 'zzz'; + } + // local!=remote or not installed -> needs update + if($localmd5 != $translationStatusArr[$lang]['md5']) { + $ret = $this->updateTranslation($extKey, $lang, $mirrorURL); + if($ret === true) { + echo (''); + } else { + echo (''); + } + continue; + } + echo (''); + } + } else { + echo (''); + } + echo (''); + $counter++; + } + echo '
' . $GLOBALS['LANG']->getLL('translation_extension_key') . ''.$LANG->getLL('lang_'.$lang,1).'
'.$extKey.'' . $GLOBALS['LANG']->getLL('translation_n_a') . '' . $GLOBALS['LANG']->getLL('translation_status_update') . + '' . $GLOBALS['LANG']->getLL('translation_status_error') . + '' . $GLOBALS['LANG']->getLL('translation_status_ok') . '' . $GLOBALS['LANG']->getLL('translation_status_could_not_fetch') . '
+ + '; + + // Fix permissions on unzipped language xml files in the entire l10n folder and all subfolders + t3lib_div::fixPermissions(PATH_typo3conf . 'l10n', TRUE); + + echo $contentParts[1] . $this->doc->endPage(); + exit; + } + + $this->content .= $this->doc->section($GLOBALS['LANG']->getLL('translation_status'), $content, 0, 1); + } + } + + /** + * Install translations for all selected languages for an extension + * + * @param string $extKey The extension key to install the translations for + * @param string $lang Language code of translation to fetch + * @param string $mirrorURL Mirror URL to fetch data from + * @return mixed true on success, error string on fauilure + */ + function updateTranslation($extKey, $lang, $mirrorURL) { + $l10n = $this->terConnection->fetchTranslation($extKey, $lang, $mirrorURL); + if(is_array($l10n)) { + $file = PATH_site.'typo3temp/'.$extKey.'-l10n-'.$lang.'.zip'; + $path = 'l10n/'.$lang.'/'; + if(!is_dir(PATH_typo3conf.$path)) t3lib_div::mkdir_deep(PATH_typo3conf,$path); + t3lib_div::writeFile($file, $l10n[0]); + if($this->unzip($file, PATH_typo3conf.$path)) { + return true; + } else { + return $GLOBALS['LANG']->getLL('translation_unpacking_failed'); + } + } else { + return $l10n; + } + } + + /** + * Install translations for all selected languages for an extension + * + * @param string $extKey The extension key to install the translations for + * @param string $mirrorURL Mirror URL to fetch data from + * @return mixed true on success, error string on fauilure + */ + function installTranslationsForExtension($extKey, $mirrorURL) { + $selectedLanguages = unserialize($this->MOD_SETTINGS['selectedLanguages']); + if(!is_array($selectedLanguages)) $selectedLanguages = array(); + foreach($selectedLanguages as $lang) { + $l10n = $this->terConnection->fetchTranslation($extKey, $lang, $mirrorURL); + if(is_array($l10n)) { + $file = PATH_typo3conf.'l10n/'.$extKey.'-l10n-'.$lang.'.zip'; + $path = 'l10n/'.$lang.'/'.$extKey; + t3lib_div::writeFile($file, $l10n[0]); + if(!is_dir(PATH_typo3conf.$path)) t3lib_div::mkdir_deep(PATH_typo3conf,$path); + if($this->unzip($file, PATH_typo3conf.$path)) { + return true; + } else { + return $GLOBALS['LANG']->getLL('translation_unpacking_failed'); + } + } else { + return $l10n; + } + } + } + + /** + * Unzips a zip file in the given path. + * + * Uses unzip binary if available, otherwise a pure PHP unzip is used. + * + * @param string $file Full path to zip file + * @param string $path Path to change to before extracting + * @return boolean True on success, false in failure + */ + function unzip($file, $path) { + if(strlen($GLOBALS['TYPO3_CONF_VARS']['BE']['unzip_path'])) { + chdir($path); + $cmd = $GLOBALS['TYPO3_CONF_VARS']['BE']['unzip_path'].' -o '.escapeshellarg($file); + exec($cmd, $list, $ret); + return ($ret === 0); + } else { + // we use a pure PHP unzip + $unzip = new em_unzip($file); + $ret = $unzip->extract(array('add_path'=>$path)); + return (is_array($ret)); + } + } + + + + /********************************* + * + * Command Applications (triggered by GET var) + * + *********************************/ + + /** + * Returns detailed info about an extension in the online repository + * + * @param string Extension repository uid + optional "private key": [uid]-[key]. + * @param [type] $version: ... + * @return void + */ + function importExtInfo($extKey, $version='') { + + $content = '
'; + + // Fetch remote data: + $this->xmlhandler->searchExtensionsXMLExact($extKey, '', '', true, true); + list($fetchData,) = $this->prepareImportExtList(true); + + $versions = array_keys($fetchData[$extKey]['versions']); + natsort($versions); + $version = ($version == '') ? end($versions) : $version; + + $opt = array(); + foreach($versions as $ver) { + $opt[]=''; + } + + // "Select version" box: + $onClick = 'window.location.href=\'index.php?CMD[importExtInfo]='.$extKey.'&CMD[extVersion]=\'+document.pageform.extVersion.options[document.pageform.extVersion.selectedIndex].value; return false;'; + $select = ' '; + + if ($this->importAtAll()) { + // Check for write-protected extension + list($inst_list,) = $this->getInstalledExtensions(); + if ($inst_list[$extKey]['EM_CONF']['state'] != 'excludeFromUpdates') { + $onClick = ' + window.location.href=\'index.php?CMD[importExt]='.$extKey.'\' + +\'&CMD[extVersion]=\'+document.pageform.extVersion.options[document.pageform.extVersion.selectedIndex].value + +\'&CMD[loc]=\'+document.pageform.loc.options[document.pageform.loc.selectedIndex].value; + return false;'; + $select .= ' ' . $GLOBALS['LANG']->getLL('ext_or') . '

+ ' . $GLOBALS['LANG']->getLL('ext_import_update_to') . ' + +
'; + } else { + $select .= '

' . $GLOBALS['LANG']->getLL('ext_import_excluded_from_updates'); + } + } else { + $select .= '

' . $this->noImportMsg(); + } + $content.= $select; + $this->content .= $this->doc->section($GLOBALS['LANG']->getLL('ext_import_select_command'), $content, 0, 1); + + // Details: + $eInfo = $fetchData[$extKey]['versions'][$version]; + $content=''.$fetchData[$extKey]['_ICON'].'  '.$eInfo['EM_CONF']['title'].' ('.$extKey.', '.$version.')

'; + $content.=$this->extInformationArray($extKey,$eInfo,1); + $this->content.=$this->doc->spacer(10); + $this->content .= $this->doc->section($GLOBALS['LANG']->getLL('ext_import_remote_ext_details'), $content, 0, 1); + } + + /** + * Fetches metadata and stores it to the corresponding place. This includes the mirror list, + * extension XML files. + * + * @param string Type of data to fetch: (mirrors) + * @param boolean If true the method doesn't produce any output + * @return void + */ + function fetchMetaData($metaType) { + global $TYPO3_CONF_VARS; + + switch($metaType) { + case 'mirrors': + $mfile = t3lib_div::tempnam('mirrors'); + $mirrorsFile = t3lib_div::getURL($this->MOD_SETTINGS['mirrorListURL'], 0, array(TYPO3_user_agent)); + if($mirrorsFile===false) { + t3lib_div::unlink_tempfile($mfile); + $content = '

' . + sprintf($GLOBALS['LANG']->getLL('ext_import_list_not_updated'), + $this->MOD_SETTINGS['mirrorListURL'] + ) . ' ' . + $GLOBALS['LANG']->getLL('translation_problems') . '

'; + } else { + t3lib_div::writeFile($mfile, $mirrorsFile); + $mirrors = implode('',gzfile($mfile)); + t3lib_div::unlink_tempfile($mfile); + + $mirrors = $this->xmlhandler->parseMirrorsXML($mirrors); + if(is_array($mirrors) && count($mirrors)) { + t3lib_BEfunc::getModuleData($this->MOD_MENU, array('extMirrors' => serialize($mirrors)), $this->MCONF['name'], '', 'extMirrors'); + $this->MOD_SETTINGS['extMirrors'] = serialize($mirrors); + $content = '

' . + sprintf($GLOBALS['LANG']->getLL('ext_import_list_updated'), + count($mirrors) + ) . '

'; + } + else { + $content = '

' . $mirrors . '
' . $GLOBALS['LANG']->getLL('ext_import_list_empty') . '

'; + } + } + break; + case 'extensions': + $this->fetchMetaData('mirrors'); // if we fetch the extensions anyway, we can as well keep this up-to-date + + $mirror = $this->getMirrorURL(); + $extfile = $mirror.'extensions.xml.gz'; + $extmd5 = t3lib_div::getURL($mirror.'extensions.md5', 0, array(TYPO3_user_agent)); + if (is_file(PATH_site.'typo3temp/extensions.xml.gz')) { + $localmd5 = md5_file(PATH_site.'typo3temp/extensions.xml.gz'); + } + + // count cached extensions. If cache is empty re-fill it + $cacheCount = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('extkey', 'cache_extensions'); + + if($extmd5 === false) { + $content .= '

' . + sprintf($GLOBALS['LANG']->getLL('ext_import_md5_not_updated'), + $mirror . 'extensions.md5' + ) . + $GLOBALS['LANG']->getLL('translation_problems') . '

'; + } elseif($extmd5 == $localmd5 && $cacheCount) { + $flashMessage = t3lib_div::makeInstance( + 't3lib_FlashMessage', + $GLOBALS['LANG']->getLL('ext_import_list_unchanged'), + $GLOBALS['LANG']->getLL('ext_import_list_unchanged_header'), + t3lib_FlashMessage::INFO + ); + $content .= $flashMessage->render(); + } else { + $extXML = t3lib_div::getURL($extfile, 0, array(TYPO3_user_agent)); + if($extXML === false) { + $content .= '

' . + sprintf($GLOBALS['LANG']->getLL('ext_import_list_unchanged'), + $extfile + ) . ' ' . + $GLOBALS['LANG']->getLL('translation_problems') . '

'; + } else { + t3lib_div::writeFile(PATH_site.'typo3temp/extensions.xml.gz', $extXML); + $content .= $this->xmlhandler->parseExtensionsXML(PATH_site.'typo3temp/extensions.xml.gz'); + } + } + break; + } + + return $content; + } + + /** + * Returns the base URL for the slected or a random mirror. + * + * @return string The URL for the selected or a random mirror + */ + function getMirrorURL() { + if(strlen($this->MOD_SETTINGS['rep_url'])) return $this->MOD_SETTINGS['rep_url']; + + $mirrors = unserialize($this->MOD_SETTINGS['extMirrors']); + if(!is_array($mirrors)) { + $this->fetchMetaData('mirrors'); + $mirrors = unserialize($this->MOD_SETTINGS['extMirrors']); + if(!is_array($mirrors)) return false; + } + if($this->MOD_SETTINGS['selectedMirror']=='') { + $rand = array_rand($mirrors); + $url = 'http://'.$mirrors[$rand]['host'].$mirrors[$rand]['path']; + } + else { + $url = 'http://'.$mirrors[$this->MOD_SETTINGS['selectedMirror']]['host'].$mirrors[$this->MOD_SETTINGS['selectedMirror']]['path']; + } + + return $url; + } + + + + /** + * Installs (activates) an extension + * + * For $mode use the three constants EM_INSTALL_VERSION_MIN, EM_INSTALL_VERSION_MAX, EM_INSTALL_VERSION_STRICT + * + * If an extension is loaded or imported already and the version requirement is matched, it will not be + * fetched from the repository. This means, if you use EM_INSTALL_VERSION_MIN, you will not always get the latest + * version of an extension! + * + * @param string $extKey The extension key to install + * @param string $version A version number that should be installed + * @param int $mode If a version is requested, this determines if it is the min, max or strict version requested + * @return [type] ... + * @todo Make the method able to handle needed interaction somehow (unmatched dependencies) + */ + function installExtension($extKey, $version=null, $mode=EM_INSTALL_VERSION_MIN) { + list($inst_list,) = $this->getInstalledExtensions(); + + // check if it is already installed and loaded with sufficient version + if(isset($inst_list[$extKey])) { + $currentVersion = $inst_list[$extKey]['EM_CONF']['version']; + + if(t3lib_extMgm::isLoaded($extKey)) { + if($version===null) { + return array(true, $GLOBALS['LANG']->getLL('ext_import_ext_already_installed_loaded')); + } else { + switch($mode) { + case EM_INSTALL_VERSION_STRICT: + if ($currentVersion == $version) { + return array(true, $GLOBALS['LANG']->getLL('ext_import_ext_already_installed_loaded')); + } + break; + case EM_INSTALL_VERSION_MIN: + if (version_compare($currentVersion, $version, '>=')) { + return array(true, $GLOBALS['LANG']->getLL('ext_import_ext_already_installed_loaded')); + } + break; + case EM_INSTALL_VERSION_MAX: + if (version_compare($currentVersion, $version, '<=')) { + return array(true, $GLOBALS['LANG']->getLL('ext_import_ext_already_installed_loaded')); + } + break; + } + } + } else { + if (!t3lib_extMgm::isLocalconfWritable()) { + return array(false, $GLOBALS['LANG']->getLL('ext_import_p_localconf')); + } + $newExtList = -1; + switch($mode) { + case EM_INSTALL_VERSION_STRICT: + if ($currentVersion == $version) { + $newExtList = $this->addExtToList($extKey, $inst_list); + } + break; + case EM_INSTALL_VERSION_MIN: + if (version_compare($currentVersion, $version, '>=')) { + $newExtList = $this->addExtToList($extKey, $inst_list); + } + break; + case EM_INSTALL_VERSION_MAX: + if (version_compare($currentVersion, $version, '<=')) { + $newExtList = $this->addExtToList($extKey, $inst_list); + } + break; + } + if ($newExtList!=-1) { + $this->writeNewExtensionList($newExtList); + $this->refreshGlobalExtList(); + $this->forceDBupdates($extKey, $inst_list[$extKey]); + return array(true, $GLOBALS['LANG']->getLL('ext_import_ext_loaded')); + } + } + } + + // at this point we know we need to import (a matching version of) the extension from TER2 + + // see if we have an extension list at all + if (!$this->xmlhandler->countExtensions()) { + $this->fetchMetaData('extensions'); + } + $this->xmlhandler->searchExtensionsXMLExact($extKey, '', '', TRUE, TRUE); + + // check if extension can be fetched + if(isset($this->xmlhandler->extensionsXML[$extKey])) { + $versions = array_keys($this->xmlhandler->extensionsXML[$extKey]['versions']); + $latestVersion = end($versions); + switch($mode) { + case EM_INSTALL_VERSION_STRICT: + if(!isset($this->xmlhandler->extensionsXML[$extKey]['versions'][$version])) { + return array(false, $GLOBALS['LANG']->getLL('ext_import_ext_n_a')); + } + break; + case EM_INSTALL_VERSION_MIN: + if (version_compare($latestVersion, $version, '>=')) { + $version = $latestVersion; + } else { + return array(false, $GLOBALS['LANG']->getLL('ext_import_ext_n_a')); + } + break; + case EM_INSTALL_VERSION_MAX: + while (($v = array_pop($versions)) && version_compare($v, $version, '>=')) { + // Loop until a version is found + } + + if ($v !== null && version_compare($v, $version, '<=')) { + $version = $v; + } else { + return array(false, $GLOBALS['LANG']->getLL('ext_import_ext_n_a')); + } + break; + } + $this->importExtFromRep($extKey, $version, 'L'); + $newExtList = $this->addExtToList($extKey, $inst_list); + if ($newExtList!=-1) { + $this->writeNewExtensionList($newExtList); + $this->refreshGlobalExtList(); + $this->forceDBupdates($extKey, $inst_list[$extKey]); + $this->installTranslationsForExtension($extKey, $this->getMirrorURL()); + return array(true, $GLOBALS['LANG']->getLL('ext_import_ext_imported')); + } else { + return array(false, $GLOBALS['LANG']->getLL('ext_import_ext_not_loaded')); + } + } else { + return array(false, $GLOBALS['LANG']->getLL('ext_import_ext_n_a_rep')); + } + } + + function refreshGlobalExtList() { + global $TYPO3_LOADED_EXT; + + $TYPO3_LOADED_EXT = t3lib_extMgm::typo3_loadExtensions(); + if ($TYPO3_LOADED_EXT['_CACHEFILE']) { + require(PATH_typo3conf.$TYPO3_LOADED_EXT['_CACHEFILE'].'_ext_localconf.php'); + } + return; + + $GLOBALS['TYPO3_LOADED_EXT'] = t3lib_extMgm::typo3_loadExtensions(); + if ($TYPO3_LOADED_EXT['_CACHEFILE']) { + require(PATH_typo3conf.$TYPO3_LOADED_EXT['_CACHEFILE'].'_ext_localconf.php'); + } else { + $temp_TYPO3_LOADED_EXT = $TYPO3_LOADED_EXT; + foreach ($temp_TYPO3_LOADED_EXT as $_EXTKEY => $temp_lEDat) { + if (is_array($temp_lEDat) && $temp_lEDat['ext_localconf.php']) { + $_EXTCONF = $TYPO3_CONF_VARS['EXT']['extConf'][$_EXTKEY]; + require($temp_lEDat['ext_localconf.php']); + } + } + } + } + + + /** + * Imports an extensions from the online repository + * NOTICE: in version 4.0 this changed from "importExtFromRep_old($extRepUid,$loc,$uploadFlag=0,$directInput='',$recentTranslations=0,$incManual=0,$dontDelete=0)" + * + * @param string Extension key + * @param string Version + * @param string Install scope: "L" or "G" or "S" + * @param boolean If true, extension is uploaded as file + * @param boolean If true, extension directory+files will not be deleted before writing the new ones. That way custom files stored in the extension folder will be kept. + * @param array Direct input array (like from kickstarter) + * @return string Return false on success, returns error message if error. + */ + function importExtFromRep($extKey,$version,$loc,$uploadFlag=0,$dontDelete=0,$directInput='') { + + $uploadSucceed = false; + $uploadedTempFile = ''; + if (is_array($directInput)) { + $fetchData = array($directInput,''); + $loc = ($loc==='G'||$loc==='S') ? $loc : 'L'; + } elseif ($uploadFlag) { + if (($uploadedTempFile = $this->CMD['alreadyUploaded']) || $_FILES['upload_ext_file']['tmp_name']) { + + // Read uploaded file: + if (!$uploadedTempFile) { + if (!is_uploaded_file($_FILES['upload_ext_file']['tmp_name'])) { + t3lib_div::sysLog('Possible file upload attack: '.$_FILES['upload_ext_file']['tmp_name'], 'Extension Manager', 3); + + return $GLOBALS['LANG']->getLL('ext_import_file_not_uploaded'); + } + + $uploadedTempFile = t3lib_div::upload_to_tempfile($_FILES['upload_ext_file']['tmp_name']); + } + $fileContent = t3lib_div::getUrl($uploadedTempFile); + + if (!$fileContent) return $GLOBALS['LANG']->getLL('ext_import_file_empty'); + + // Decode file data: + $fetchData = $this->terConnection->decodeExchangeData($fileContent); + + if (is_array($fetchData)) { + $extKey = $fetchData[0]['extKey']; + if ($extKey) { + if (!$this->CMD['uploadOverwrite']) { + $loc = ($loc==='G'||$loc==='S') ? $loc : 'L'; + $comingExtPath = PATH_site.$this->typePaths[$loc].$extKey.'/'; + if (@is_dir($comingExtPath)) { + $flashMessage = t3lib_div::makeInstance( + 't3lib_FlashMessage', + sprintf($GLOBALS['LANG']->getLL('ext_import_ext_present_no_overwrite'), $comingExtPath) . + '
' . $GLOBALS['LANG']->getLL('ext_import_ext_present_nothing_done'), + '', + t3lib_FlashMessage::ERROR + ); + return $flashMessage->render(); + } // ... else go on, install... + } // ... else go on, install... + } else return $GLOBALS['LANG']->getLL('ext_import_no_key'); + } else return sprintf($GLOBALS['LANG']->getLL('ext_import_wrong_file_format'), $fetchData); + } else return $GLOBALS['LANG']->getLL('ext_import_no_file'); + } else { + $this->xmlhandler->searchExtensionsXMLExact($extKey, '', '', true, true); + + // Fetch extension from TER: + if(!strlen($version)) { + $versions = array_keys($this->xmlhandler->extensionsXML[$extKey]['versions']); + $version = end($versions); + } + $fetchData = $this->terConnection->fetchExtension($extKey, $version, $this->xmlhandler->extensionsXML[$extKey]['versions'][$version]['t3xfilemd5'], $this->getMirrorURL()); + } + + // At this point the extension data should be present; so we want to write it to disc: + if ($this->importAsType($loc)) { + if (is_array($fetchData)) { // There was some data successfully transferred + if ($fetchData[0]['extKey'] && is_array($fetchData[0]['FILES'])) { + $extKey = $fetchData[0]['extKey']; + if(!isset($fetchData[0]['EM_CONF']['constraints'])) $fetchData[0]['EM_CONF']['constraints'] = $this->xmlhandler->extensionsXML[$extKey]['versions'][$version]['dependencies']; + $EM_CONF = $this->fixEMCONF($fetchData[0]['EM_CONF']); + if (!$EM_CONF['lockType'] || !strcmp($EM_CONF['lockType'],$loc)) { + // check dependencies, act accordingly if ext is loaded + list($instExtInfo,)=$this->getInstalledExtensions(); + $depStatus = $this->checkDependencies($extKey, $EM_CONF, $instExtInfo); + if(t3lib_extMgm::isLoaded($extKey) && !$depStatus['returnCode']) { + $this->content .= $depStatus['html']; + if ($uploadedTempFile) { + $this->content .= ''; + } + } else { + $res = $this->clearAndMakeExtensionDir($fetchData[0],$loc,$dontDelete); + if (is_array($res)) { + $extDirPath = trim($res[0]); + if ($extDirPath && @is_dir($extDirPath) && substr($extDirPath,-1)=='/') { + + $emConfFile = $this->construct_ext_emconf_file($extKey,$EM_CONF); + $dirs = $this->extractDirsFromFileList(array_keys($fetchData[0]['FILES'])); + + $res = $this->createDirsInPath($dirs,$extDirPath); + if (!$res) { + $writeFiles = $fetchData[0]['FILES']; + $writeFiles['ext_emconf.php']['content'] = $emConfFile; + $writeFiles['ext_emconf.php']['content_md5'] = md5($emConfFile); + + // Write files: + foreach($writeFiles as $theFile => $fileData) { + t3lib_div::writeFile($extDirPath.$theFile,$fileData['content']); + if (!@is_file($extDirPath.$theFile)) { + $content .= sprintf($GLOBALS['LANG']->getLL('ext_import_file_not_created'), + $extDirPath . $theFile) . '
'; + } elseif (md5(t3lib_div::getUrl($extDirPath.$theFile)) != $fileData['content_md5']) { + $content .= sprintf($GLOBALS['LANG']->getLL('ext_import_file_corrupted'), + $extDirPath . $theFile) . '
'; + } + } + + t3lib_div::fixPermissions($extDirPath, TRUE); + + // No content, no errors. Create success output here: + if (!$content) { + $messageContent = sprintf($GLOBALS['LANG']->getLL('ext_import_success_folder'), $extDirPath) . '
'; + + $uploadSucceed = true; + + // Fix TYPO3_MOD_PATH for backend modules in extension: + $modules = t3lib_div::trimExplode(',', $EM_CONF['module'], 1); + if (count($modules)) { + foreach($modules as $mD) { + $confFileName = $extDirPath . $mD . '/conf.php'; + if (@is_file($confFileName)) { + $messageContent .= $this->writeTYPO3_MOD_PATH($confFileName, $loc, $extKey . '/' . $mD . '/') . '
'; + } else { + $messageContent .= sprintf($GLOBALS['LANG']->getLL('ext_import_no_conf_file'), + $confFileName) . '
'; + } + } + } + // NOTICE: I used two hours trying to find out why a script, ext_emconf.php, written twice and in between included by PHP did not update correct the second time. Probably something with PHP-A cache and mtime-stamps. + // But this order of the code works.... (using the empty Array with type, EMCONF and files hereunder). + + // Writing to ext_emconf.php: + $sEMD5A = $this->serverExtensionMD5Array($extKey,array('type' => $loc, 'EM_CONF' => array(), 'files' => array())); + $EM_CONF['_md5_values_when_last_written'] = serialize($sEMD5A); + $emConfFile = $this->construct_ext_emconf_file($extKey, $EM_CONF); + t3lib_div::writeFile($extDirPath . 'ext_emconf.php', $emConfFile); + + $messageContent .= 'ext_emconf.php: ' . $extDirPath . 'ext_emconf.php
'; + $messageContent .= $GLOBALS['LANG']->getLL('ext_import_ext_type') . ' '; + $messageContent .= $this->typeLabels[$loc] . '
'; + $messageContent .= '
'; + + // Remove cache files: + $updateContent = ''; + if (t3lib_extMgm::isLoaded($extKey)) { + if ($this->removeCacheFiles()) { + $messageContent .= $GLOBALS['LANG']->getLL('ext_import_cache_files_removed') . '
'; + } + + list($new_list) = $this->getInstalledExtensions(); + $updateContent = $this->updatesForm($extKey, $new_list[$extKey], 1, 'index.php?CMD[showExt]=' . $extKey . '&SET[singleDetails]=info'); + } + + $flashMessage = t3lib_div::makeInstance( + 't3lib_FlashMessage', + $messageContent, + $GLOBALS['LANG']->getLL('ext_import_success') + ); + $content = $flashMessage->render() . $updateContent; + + + // Install / Uninstall: + if(!$this->CMD['standAlone']) { + $content .= '

' . $GLOBALS['LANG']->getLL('ext_import_install_uninstall') . '

'; + $content.= $new_list[$extKey] ? + '' . + $this->removeButton() . ' ' . $GLOBALS['LANG']->getLL('ext_import_uninstall') . '' : + '' . + $this->installButton() . ' ' . $GLOBALS['LANG']->getLL('ext_import_install') . ''; + } else { + $content = $GLOBALS['LANG']->getLL('ext_import_imported') . + '

' . + $GLOBALS['LANG']->getLL('ext_import_close_check') . ''; + } + } + } else $content = $res; + } else $content = sprintf($GLOBALS['LANG']->getLL('ext_import_ext_path_different'), $extDirPath); + } else $content = $res; + } + } else $content = sprintf($GLOBALS['LANG']->getLL('ext_import_ext_only_here'), + $this->typePaths[$EM_CONF['lockType']], $EM_CONF['lockType']); + } else $content = $GLOBALS['LANG']->getLL('ext_import_no_ext_key_files'); + } else $content = sprintf($GLOBALS['LANG']->getLL('ext_import_data_transfer'), $fetchData); + } else $content = sprintf($GLOBALS['LANG']->getLL('ext_import_no_install_here'), $this->typePaths[$loc]); + + $this->content .= $this->doc->section($GLOBALS['LANG']->getLL('ext_import_results'), $content, 0, 1); + + if ($uploadSucceed && $uploadedTempFile) { + t3lib_div::unlink_tempfile($uploadedTempFile); + } + + return false; + } + + /** + * Display extensions details. + * + * @param string Extension key + * @return void Writes content to $this->content + */ + function showExtDetails($extKey) { + global $TYPO3_LOADED_EXT; + + list($list,)=$this->getInstalledExtensions(); + $absPath = $this->getExtPath($extKey,$list[$extKey]['type']); + + // Check updateModule: + if (isset($list[$extKey]) && @is_file($absPath.'class.ext_update.php')) { + require_once($absPath.'class.ext_update.php'); + $updateObj = new ext_update; + if (!$updateObj->access()) { + unset($this->MOD_MENU['singleDetails']['updateModule']); + } + } else { + unset($this->MOD_MENU['singleDetails']['updateModule']); + } + + if($this->CMD['doDelete']) { + $this->MOD_MENU['singleDetails'] = array(); + } + + // Function menu here: + if(!$this->CMD['standAlone'] && !t3lib_div::_GP('standAlone')) { + $content = $GLOBALS['LANG']->getLL('ext_details_ext') . ' ' . + $this->extensionTitleIconHeader($extKey, $list[$extKey]) . ' (' . $extKey . ')'; + $this->content.= $this->doc->section('', $content); + } + + // Show extension details: + if ($list[$extKey]) { + + // Checking if a command for install/uninstall is executed: + if (($this->CMD['remove'] || $this->CMD['load']) && !in_array($extKey,$this->requiredExt)) { + + // Install / Uninstall extension here: + if (t3lib_extMgm::isLocalconfWritable()) { + // Check dependencies: + $depStatus = $this->checkDependencies($extKey, $list[$extKey]['EM_CONF'], $list); + if(!$this->CMD['remove'] && !$depStatus['returnCode']) { + $this->content .= $depStatus['html']; + $newExtList = -1; + } elseif ($this->CMD['remove']) { + $newExtList = $this->removeExtFromList($extKey,$list); + } else { + $newExtList = $this->addExtToList($extKey,$list); + } + + // Successful installation: + if ($newExtList!=-1) { + $updates = ''; + if ($this->CMD['load']) { + if($_SERVER['REQUEST_METHOD'] == 'POST') { + $script = t3lib_div::linkThisScript(array('CMD[showExt]' => $extKey, 'CMD[load]' => 1, 'CMD[clrCmd]' => $this->CMD['clrCmd'], 'SET[singleDetails]' => 'info')); + } else { + $script = ''; + } + if($this->CMD['standAlone']) { + $standaloneUpdates = ''; + } + $depsolver = t3lib_div::_POST('depsolver'); + if(is_array($depsolver['ignore'])) { + foreach($depsolver['ignore'] as $depK => $depV) { + $dependencyUpdates .= ''; + } + } + $updatesForm = $this->updatesForm($extKey,$list[$extKey],1,$script, $dependencyUpdates.$standaloneUpdates.'', TRUE); + if ($updatesForm) { + $updates = $GLOBALS['LANG']->getLL('ext_details_new_tables_fields') . '
' . + $GLOBALS['LANG']->getLL('ext_details_new_tables_fields_select') . $updatesForm; + $labelDBUpdate = $GLOBALS['LANG']->csConvObj->conv_case( + $GLOBALS['LANG']->charSet, + $GLOBALS['LANG']->getLL('ext_details_db_needs_update'), + 'toUpper' + ); + $this->content .= $this->doc->section( + sprintf($GLOBALS['LANG']->getLL('ext_details_installing') . ' ', + $this->extensionTitleIconHeader($extKey, $list[$extKey]) + ) . ' ' . + $labelDBUpdate, + $updates, 1, 1, 1, 1 + ); + } + } elseif ($this->CMD['remove']) { + $updates.= $this->checkClearCache($list[$extKey]); + if ($updates) { + $updates = ' +
'.$updates.' +
+ + + +
'; + $labelDBUpdate = $GLOBALS['LANG']->csConvObj->conv_case( + $GLOBALS['LANG']->charSet, + $GLOBALS['LANG']->getLL('ext_details_db_needs_update'), + 'toUpper' + ); + $this->content .= $this->doc->section( + sprintf($GLOBALS['LANG']->getLL('ext_details_removing') . ' ', + $this->extensionTitleIconHeader($extKey, $list[$extKey]) + ) . ' ' . + $labelDBUpdate, + $updates, 1, 1, 1, 1 + ); + } + } + if (!$updates || t3lib_div::_GP('_do_install')) { + $this->writeNewExtensionList($newExtList); + $action = $this->CMD['load'] ? 'installed' : 'removed'; + $GLOBALS['BE_USER']->writelog(5, 1, 0, 0, 'Extension list has been changed, extension %s has been %s', array($extKey, $action)); + + $messageLabel = 'ext_details_ext_' . $action . '_with_key'; + $flashMessage = t3lib_div::makeInstance( + 't3lib_FlashMessage', + sprintf($GLOBALS['LANG']->getLL($messageLabel), $extKey), + '', + t3lib_FlashMessage::OK, + TRUE + ); + t3lib_FlashMessageQueue::addMessage($flashMessage); + + if ($this->CMD['clrCmd'] || t3lib_div::_GP('_clrCmd')) { + if ($this->CMD['load'] && @is_file($absPath.'ext_conf_template.txt')) { + $vA = array('CMD'=>Array('showExt'=>$extKey)); + } else { + $vA = array('CMD'=>''); + } + } else { + $vA = array('CMD'=>Array('showExt'=>$extKey)); + } + if($this->CMD['standAlone'] || t3lib_div::_GP('standAlone')) { + $this->content .= sprintf($GLOBALS['LANG']->getLL('ext_details_ext_installed_removed'), + ($this->CMD['load'] ? + $GLOBALS['LANG']->getLL('ext_details_installed') : + $GLOBALS['LANG']->getLL('ext_details_removed') + ) + ) . + '

' . + $GLOBALS['LANG']->getLL('ext_import_close_check') . ''; + } else { + // Determine if new modules were installed: + $techInfo = $this->makeDetailedExtensionAnalysis($extKey, $list[$extKey]); + if (($this->CMD['load'] || $this->CMD['remove']) && is_array($techInfo['flags']) && in_array('Module', $techInfo['flags'], true)) { + $vA['CMD']['refreshMenu'] = 1; + } + t3lib_utility_Http::redirect(t3lib_div::linkThisScript($vA)); + } + } + } + } else { + $writeAccessError = $GLOBALS['LANG']->csConvObj->conv_case( + $GLOBALS['LANG']->charSet, + $GLOBALS['LANG']->getLL('ext_details_write_access_error'), + 'toUpper' + ); + $this->content .= $this->doc->section( + sprintf($GLOBALS['LANG']->getLL('ext_details_installing') . ' ', + $this->extensionTitleIconHeader($extKey, $list[$extKey]) + ) . ' ' . + $writeAccessError, + $GLOBALS['LANG']->getLL('ext_details_write_error_localconf'), + 1, 1, 2, 1 + ); + } + + } elseif ($this->CMD['downloadFile'] && !in_array($extKey,$this->requiredExt)) { + + // Link for downloading extension has been clicked - deliver content stream: + $dlFile = $this->CMD['downloadFile']; + if (t3lib_div::isFirstPartOfStr($dlFile,PATH_site) && t3lib_div::isFirstPartOfStr($dlFile,$absPath) && @is_file($dlFile)) { + $mimeType = 'application/octet-stream'; + Header('Content-Type: '.$mimeType); + Header('Content-Disposition: attachment; filename='.basename($dlFile)); + echo t3lib_div::getUrl($dlFile); + exit; + } else { + throw new RuntimeException( + 'TYPO3 Fatal Error: ' . $GLOBALS['LANG']->getLL('ext_details_error_downloading'), + 1270853980 + ); + } + + } elseif ($this->CMD['editFile'] && !in_array($extKey,$this->requiredExt)) { + + // Editing extension file: + $editFile = $this->CMD['editFile']; + if (t3lib_div::isFirstPartOfStr($editFile,PATH_site) && t3lib_div::isFirstPartOfStr($editFile,$absPath)) { // Paranoia... + + $fI = t3lib_div::split_fileref($editFile); + if (@is_file($editFile) && t3lib_div::inList($this->editTextExtensions,($fI['fileext']?$fI['fileext']:$fI['filebody']))) { + if (filesize($editFile)<($this->kbMax*1024)) { + $outCode = '
'; + $info = ''; + $submittedContent = t3lib_div::_POST('edit'); + $saveFlag = 0; + + if(isset($submittedContent['file']) && !$GLOBALS['TYPO3_CONF_VARS']['EXT']['noEdit']) { // Check referer here? + $oldFileContent = t3lib_div::getUrl($editFile); + if($oldFileContent != $submittedContent['file']) { + $oldMD5 = md5(str_replace(CR,'',$oldFileContent)); + $info .= sprintf( + $GLOBALS['LANG']->getLL('ext_details_md5_previous'), + '' . $oldMD5 . '' + ) . '
'; + t3lib_div::writeFile($editFile,$submittedContent['file']); + $saveFlag = 1; + } else { + $info .= $GLOBALS['LANG']->getLL('ext_details_no_changes') . '
'; + } + } + + $fileContent = t3lib_div::getUrl($editFile); + + $outCode.= sprintf( + $GLOBALS['LANG']->getLL('ext_details_file'), + '' . substr($editFile, strlen($absPath)) . ' (' . + t3lib_div::formatSize(filesize($editFile)) . ')
' + ); + $fileMD5 = md5(str_replace(CR,'',$fileContent)); + $info .= sprintf( + $GLOBALS['LANG']->getLL('ext_details_md5_current'), + '' . $fileMD5 . '' + ) . '
'; + if($saveFlag) { + $saveMD5 = md5(str_replace(CR,'',$submittedContent['file'])); + $info .= sprintf( + $GLOBALS['LANG']->getLL('ext_details_md5_submitted'), + '' . $saveMD5 . '' + ) . '
'; + if ($fileMD5!=$saveMD5) { + $info .= $GLOBALS['TBE_TEMPLATE']->rfw( + '
' . $GLOBALS['LANG']->getLL('ext_details_saving_failed_changes_lost') . '' + ) . '
'; + } + else { + $info .= $GLOBALS['TBE_TEMPLATE']->rfw( + '
' . $GLOBALS['LANG']->getLL('ext_details_file_saved') . '' + ) . '
'; + } + } + + $outCode.= ''; + $outCode.= ''; + $outCode.= ''; + $outCode.= ''; + $outCode.= $info; + + if (!$GLOBALS['TYPO3_CONF_VARS']['EXT']['noEdit']) { + $outCode .= '
'; + } + else { + $outCode .= $GLOBALS['TBE_TEMPLATE']->rfw( + '
' . $GLOBALS['LANG']->getLL('ext_details_saving_disabled') . ' ' + ); + } + + $onClick = 'window.location.href=\'index.php?CMD[showExt]='.$extKey.'\';return false;'; + $outCode .= '
'; + + $theOutput.=$this->doc->spacer(15); + $theOutput .= $this->doc->section($GLOBALS['LANG']->getLL('ext_details_edit_file'), '', 0, 1); + $theOutput.=$this->doc->sectionEnd().$outCode; + $this->content.=$theOutput; + } else { + $theOutput.=$this->doc->spacer(15); + $theOutput .= $this->doc->section( + sprintf( + $GLOBALS['LANG']->getLL('ext_details_filesize_exceeded_kb'), + $this->kbMax + ), + sprintf( + $GLOBALS['LANG']->getLL('ext_details_file_too_large'), + $this->kbMax + ) + ); + } + } + } else { + die (sprintf($GLOBALS['LANG']->getLL('ext_details_fatal_edit_error'), + $editFile + ) + ); + } + } else { + + // MAIN: + switch((string)$this->MOD_SETTINGS['singleDetails']) { + case 'info': + // Loaded / Not loaded: + if (!in_array($extKey,$this->requiredExt)) { + if ($TYPO3_LOADED_EXT[$extKey]) { + $content = '' . $GLOBALS['LANG']->getLL('ext_details_loaded_and_running') . '
' . + '' . $GLOBALS['LANG']->getLL('ext_details_remove_button') . ' ' . $this->removeButton() . ''; + } else { + $content = $GLOBALS['LANG']->getLL('ext_details_not_loaded') . '
'. + '' . $GLOBALS['LANG']->getLL('ext_details_install_button') . ' ' . $this->installButton() . ''; + } + } else { + $content = $GLOBALS['LANG']->getLL('ext_details_always_loaded'); + } + $this->content.=$this->doc->spacer(10); + $this->content .= $this->doc->section( + $GLOBALS['LANG']->getLL('ext_details_current_status'), $content, 0, 1 + ); + + if (t3lib_extMgm::isLoaded($extKey)) { + $updates=$this->updatesForm($extKey,$list[$extKey]); + if ($updates) { + $this->content.=$this->doc->spacer(10); + $this->content .= $this->doc->section( + $GLOBALS['LANG']->getLL('ext_details_update_needed'), + $updates . '

' . $GLOBALS['LANG']->getLL('ext_details_notice_static_data'), + 0, 1 + ); + } + } + + // Config: + if (@is_file($absPath.'ext_conf_template.txt')) { + $this->content.=$this->doc->spacer(10); + $this->content .= $this->doc->section( + $GLOBALS['LANG']->getLL('ext_details_configuration'), + $GLOBALS['LANG']->getLL('ext_details_notice_clear_cache') . '

', + 0, 1 + ); + + $this->tsStyleConfigForm($extKey, $list[$extKey]); + } + + // Show details: + $content = t3lib_BEfunc::cshItem('_MOD_tools_em', 'info', $GLOBALS['BACK_PATH'], '|
'); + $content.= $this->extInformationArray($extKey,$list[$extKey]); + + $this->content.=$this->doc->spacer(10); + $this->content .= $this->doc->section( + $GLOBALS['LANG']->getLL('ext_details_details'), $content, 0, 1 + ); + break; + case 'upload': + $em = t3lib_div::_POST('em'); + if($em['action'] == 'doUpload') { + $em['extKey'] = $extKey; + $em['extInfo'] = $list[$extKey]; + $content = $this->uploadExtensionToTER($em); + $content .= $this->doc->spacer(10); + // Must reload this, because EM_CONF information has been updated! + list($list,)=$this->getInstalledExtensions(); + } else { + // CSH: + $content = t3lib_BEfunc::cshItem('_MOD_tools_em', 'upload', $GLOBALS['BACK_PATH'], '|
'); + + // Upload: + if (substr($extKey,0,5)!='user_') { + $content.= $this->getRepositoryUploadForm($extKey,$list[$extKey]); + $eC=0; + } else { + $content .= $GLOBALS['LANG']->getLL('ext_details_no_unique_ext'); + $eC=2; + } + if (!$this->fe_user['username']) { + $flashMessage = t3lib_div::makeInstance( + 't3lib_FlashMessage', + sprintf($GLOBALS['LANG']->getLL('ext_details_no_username'), + '', '' + ), + '', + t3lib_FlashMessage::INFO + ); + $content .= '
' . $flashMessage->render(); + + } + } + $this->content .= $this->doc->section( + $GLOBALS['LANG']->getLL('ext_details_upload_to_ter'), + $content, 0, 1, $eC + ); + break; + case 'backup': + if($this->CMD['doDelete']) { + $content = $this->extDelete($extKey,$list[$extKey]); + $this->content .= $this->doc->section( + $GLOBALS['LANG']->getLL('ext_details_delete'), + $content, 0, 1 + ); + } else { + $csh = t3lib_BEfunc::cshItem('_MOD_tools_em', 'backup_delete', $GLOBALS['BACK_PATH'], '|
'); + $content = $this->extBackup($extKey, $list[$extKey]); + $this->content .= $this->doc->section( + $GLOBALS['LANG']->getLL('ext_details_backup') . ' ' . $csh, + $content, 0, 1, 0, 1 + ); + + $content = $this->extDelete($extKey,$list[$extKey]); + $this->content .= $this->doc->section( + $GLOBALS['LANG']->getLL('ext_details_delete'), + $content, 0, 1 + ); + + $content = $this->extUpdateEMCONF($extKey,$list[$extKey]); + $this->content .= $this->doc->section( + $GLOBALS['LANG']->getLL('ext_details_update_em_conf'), + $content, 0, 1 + ); + } + break; + case 'dump': + $this->extDumpTables($extKey,$list[$extKey]); + break; + case 'edit': + $content = t3lib_BEfunc::cshItem('_MOD_tools_em', 'editfiles', $GLOBALS['BACK_PATH'], '|
'); + $content.= $this->getFileListOfExtension($extKey,$list[$extKey]); + + $this->content.=$this->doc->section( + $GLOBALS['LANG']->getLL('ext_details_ext_files'), + $content, 0, 1 + ); + break; + case 'updateModule': + $this->content .= $this->doc->section( + $GLOBALS['LANG']->getLL('ext_details_update'), + is_object($updateObj) ? + $updateObj->main() : + $GLOBALS['LANG']->getLL('ext_details_no_update_object'), + 0, 1 + ); + break; + default: + $this->extObjContent(); + break; + } + } + } + } + + /** + * Outputs a screen from where you can install multiple extensions in one go + * This can be called from external modules with "...index.php?CMD[requestInstallExtensions]= + * + * @param string Comma list of extension keys to install. Renders a screen with checkboxes for all extensions not already imported or installed + * @return void + */ + function requestInstallExtensions($extList) { + + // Return URL: + $returnUrl = t3lib_div::_GP('returnUrl'); + $installOrImportExtension = t3lib_div::_POST('installOrImportExtension'); + + // Extension List: + $extArray = explode(',',$extList); + $outputRow = array(); + $outputRow[] = ' + + ' . $GLOBALS['LANG']->getLL('reqInstExt_install_import') . ' + ' . $GLOBALS['LANG']->getLL('reqInstExt_ext_key') . ' + + '; + + foreach($extArray as $extKey) { + + // Check for the request: + if ($installOrImportExtension[$extKey]) { + $this->installExtension($extKey); + } + + // Display: + if (!t3lib_extMgm::isLoaded($extKey)) { + $outputRow[] = ' + + + + + '; + } + } + + if (count($outputRow)>1 || !$returnUrl) { + $content = ' + +
+ '.implode('',$outputRow).'
+ +
'; + + if ($returnUrl) { + $content.= ' +
+
+ ' . $GLOBALS['LANG']->getLL('reqInstExt_return') . ' + '; + } + + $this->content.= $this->doc->section( + $GLOBALS['LANG']->getLL('reqInstExt_imp_inst_ext'), $content, 0, 1 + ); + } else { + t3lib_utility_Http::redirect($returnUrl); + } + } + + + + + + + + + /*********************************** + * + * Application Sub-functions (HTML parts) + * + **********************************/ + + /** + * Creates a form for an extension which contains all options for configuration, updates of database, clearing of cache etc. + * This form is shown when + * + * @param string Extension key + * @param array Extension information array + * @param boolean If set, the form will ONLY show if fields/tables should be updated (suppressing forms like general configuration and cache clearing). + * @param string Alternative action=""-script + * @param string HTML: Additional form fields + * @return string HTML + */ + function updatesForm($extKey,$extInfo,$notSilent=0,$script='',$addFields='') { + $script = $script ? $script : t3lib_div::linkThisScript(); + $updates.= $this->checkDBupdates($extKey,$extInfo); + $uCache = $this->checkClearCache($extInfo); + if ($notSilent) $updates.= $uCache; + $updates.= $this->checkUploadFolder($extKey,$extInfo); + + $absPath = $this->getExtPath($extKey, $extInfo['type']); + if ($notSilent && @is_file($absPath.'ext_conf_template.txt')) { + $configForm = $this->tsStyleConfigForm($extKey, $extInfo, 1, $script, $updates.$addFields.'
'); + } + + if ($updates || $configForm) { + if ($configForm) { + $updates = ''.$configForm.'
'; + } else { + $updates = '
'.$updates.$addFields.' +
+ '; + } + } + + return $updates; + } + + /** + * Creates view for dumping static tables and table/fields structures... + * + * @param string Extension key + * @param array Extension information array + * @return void + */ + function extDumpTables($extKey,$extInfo) { + + // Get dbInfo which holds the structure known from the tables.sql file + $techInfo = $this->makeDetailedExtensionAnalysis($extKey,$extInfo); + $absPath = $this->getExtPath($extKey,$extInfo['type']); + + // Static tables: + if (is_array($techInfo['static'])) { + if ($this->CMD['writeSTATICdump']) { // Writing static dump: + $writeFile = $absPath.'ext_tables_static+adt.sql'; + if (@is_file($writeFile)) { + $dump_static = $this->dumpStaticTables(implode(',',$techInfo['static'])); + t3lib_div::writeFile($writeFile,$dump_static); + $this->content .= $this->doc->section( + $GLOBALS['LANG']->getLL('extDumpTables_tables_fields'), + sprintf($GLOBALS['LANG']->getLL('extDumpTables_bytes_written_to'), + t3lib_div::formatSize(strlen($dump_static)), + substr($writeFile, strlen(PATH_site)) + ), + 0, 1 + ); + } + } else { // Showing info about what tables to dump - and giving the link to execute it. + $msg = $GLOBALS['LANG']->getLL('extDumpTables_dumping_content') . '
'; + $msg.= '
'.implode('
',$techInfo['static']).'
'; + + // ... then feed that to this function which will make new CREATE statements of the same fields but based on the current database content. + $this->content .= $this->doc->section( + $GLOBALS['LANG']->getLL('extDumpTables_static_tables'), + $msg . '
' . $GLOBALS['LANG']->getLL('extDumpTables_write_static') . '', + 0, 1 + ); + $this->content.=$this->doc->spacer(20); + } + } + + // Table and field definitions: + if (is_array($techInfo['dump_tf'])) { + $dump_tf_array = $this->getTableAndFieldStructure($techInfo['dump_tf']); + $dump_tf = $this->dumpTableAndFieldStructure($dump_tf_array); + if ($this->CMD['writeTFdump']) { + $writeFile = $absPath.'ext_tables.sql'; + if (@is_file($writeFile)) { + t3lib_div::writeFile($writeFile,$dump_tf); + $this->content .= $this->doc->section( + $GLOBALS['LANG']->getLL('extDumpTables_tables_fields'), + sprintf($GLOBALS['LANG']->getLL('extDumpTables_bytes_written_to'), + t3lib_div::formatSize(strlen($dump_tf)), + substr($writeFile, strlen(PATH_site)) + ), + 0, 1 + ); + } + } else { + $msg = $GLOBALS['LANG']->getLL('extDumpTables_dumping_db_structure') . '
'; + if (is_array($techInfo['tables'])) { + $msg .= '
' . $GLOBALS['LANG']->getLL('extDumpTables_tables') . '
' . + implode('
', $techInfo['tables']) . '
'; + } + if (is_array($techInfo['fields'])) { + $msg .= '
' . $GLOBALS['LANG']->getLL('extDumpTables_solo_fields') . '
' . + implode('
', $techInfo['fields']) . '
'; + } + + // ... then feed that to this function which will make new CREATE statements of the same fields but based on the current database content. + $this->content.=$this->doc->section( + $GLOBALS['LANG']->getLL('extDumpTables_tables_fields'), + $msg . '
' . $GLOBALS['LANG']->getLL('extDumpTables_write_dump') . '
+
' . htmlspecialchars($dump_tf) . '
', + 0, 1 + ); + + + $details = ' ' . $GLOBALS['LANG']->getLL('extDumpTables_based_on') . '
+
    +
  • ' . $GLOBALS['LANG']->getLL('extDumpTables_based_on_one') . '
  • +
  • ' . $GLOBALS['LANG']->getLL('extDumpTables_based_on_two') . '
  • +
+ ' . $GLOBALS['LANG']->getLL('extDumpTables_bottomline') . '
'; + $this->content.=$this->doc->section('',$details); + } + } + } + + /** + * Returns file-listing of an extension + * + * @param string Extension key + * @param array Extension information array + * @return string HTML table. + */ + function getFileListOfExtension($extKey,$conf) { + $content = ''; + $extPath = $this->getExtPath($extKey,$conf['type']); + + if ($extPath) { + // Read files: + $fileArr = array(); + $fileArr = t3lib_div::getAllFilesAndFoldersInPath($fileArr,$extPath,'',0,99,$this->excludeForPackaging); + + // Start table: + $lines = array(); + $totalSize = 0; + + // Header: + $lines[] = ' + + ' . $GLOBALS['LANG']->getLL('extFileList_file') . ' + ' . $GLOBALS['LANG']->getLL('extFileList_size') . ' + ' . $GLOBALS['LANG']->getLL('extFileList_edit') . ' + '; + + foreach($fileArr as $file) { + $fI = t3lib_div::split_fileref($file); + $lines[] = ' + + ' . + substr($file, strlen($extPath)) . ' + '.t3lib_div::formatSize(filesize($file)).' + ' . (!in_array($extKey, $this->requiredExt) && + t3lib_div::inList($this->editTextExtensions, + ($fI['fileext'] ? $fI['fileext'] : $fI['filebody'])) ? + '' . + $GLOBALS['LANG']->getLL('extFileList_edit_file') . '' : '' + ) . ' + '; + $totalSize+=filesize($file); + } + + $lines[] = ' + + ' . $GLOBALS['LANG']->getLL('extFileList_total') . ' + '.t3lib_div::formatSize($totalSize).' +   + '; + + $content = ' + Path: '.$extPath.'

+ '.implode('',$lines).'
'; + } + + return $content; + } + + /** + * Delete extension from the file system + * + * @param string Extension key + * @param array Extension info array + * @return string Returns message string about the status of the operation + */ + function extDelete($extKey,$extInfo) { + $absPath = $this->getExtPath($extKey,$extInfo['type']); + if (t3lib_extMgm::isLoaded($extKey)) { + return $GLOBALS['LANG']->getLL('extDelete_ext_active'); + } elseif (!$this->deleteAsType($extInfo['type'])) { + return sprintf($GLOBALS['LANG']->getLL('extDelete_wrong_scope'), + $this->typeLabels[$extInfo['type']] + ); + } elseif (t3lib_div::inList('G,L',$extInfo['type'])) { + if ($this->CMD['doDelete'] && !strcmp($absPath,$this->CMD['absPath'])) { + $res = $this->removeExtDirectory($absPath); + if ($res) { + $flashMessage = t3lib_div::makeInstance( + 't3lib_FlashMessage', + nl2br($res), + sprintf($GLOBALS['LANG']->getLL('extDelete_remove_dir_failed'), $absPath), + t3lib_FlashMessage::ERROR + ); + return $flashMessage->render(); + } else { + $flashMessage = t3lib_div::makeInstance( + 't3lib_FlashMessage', + sprintf($GLOBALS['LANG']->getLL('extDelete_removed'), $absPath), + $GLOBALS['LANG']->getLL('extDelete_removed_header'), + t3lib_FlashMessage::OK + ); + return $flashMessage->render(); + } + } else { + $areYouSure = $GLOBALS['LANG']->getLL('extDelete_sure'); + $deleteFromServer = $GLOBALS['LANG']->getLL('extDelete_from_server'); + $onClick = "if (confirm('$areYouSure')) {window.location.href='index.php?CMD[showExt]=" . + $extKey . '&CMD[doDelete]=1&CMD[absPath]=' . rawurlencode($absPath) . "';}"; + $content .= '' . $deleteFromServer . ' ' . + sprintf($GLOBALS['LANG']->getLL('extDelete_from_location'), + $this->typeLabels[$extInfo['type']], + substr($absPath,strlen(PATH_site)) + ) . ''; + $content .= '

' . $GLOBALS['LANG']->getLL('extDelete_backup'); + return $content; + } + } else return $GLOBALS['LANG']->getLL('extDelete_neither_global_nor_local'); + } + + /** + * Update extension EM_CONF... + * + * @param string Extension key + * @param array Extension information array + * @return string HTML content. + */ + function extUpdateEMCONF($extKey,$extInfo) { + $absPath = $this->getExtPath($extKey,$extInfo['type']); + if ($this->CMD['doUpdateEMCONF']) { + return $this->updateLocalEM_CONF($extKey,$extInfo); + } else { + $sure = $GLOBALS['LANG']->getLL('extUpdateEMCONF_sure'); + $updateEMConf = $GLOBALS['LANG']->getLL('extUpdateEMCONF_file'); + $onClick = "if (confirm('$sure')) {window.location.href='index.php?CMD[showExt]=" . + $extKey . "&CMD[doUpdateEMCONF]=1';}"; + $content .= '' . $updateEMConf . ' ' . + sprintf($GLOBALS['LANG']->getLL('extDelete_from_location'), + $this->typeLabels[$extInfo['type']], + substr($absPath, strlen(PATH_site)) + ) . ''; + $content .= '

' . $GLOBALS['LANG']->getLL('extUpdateEMCONF_info_changes') . '
+ ' . $GLOBALS['LANG']->getLL('extUpdateEMCONF_info_reset'); + return $content; + } + } + + /** + * Download extension as file / make backup + * + * @param string Extension key + * @param array Extension information array + * @return string HTML content + */ + function extBackup($extKey,$extInfo) { + $uArr = $this->makeUploadArray($extKey,$extInfo); + if (is_array($uArr)) { + $backUpData = $this->terConnection->makeUploadDataFromArray($uArr); + $filename = 'T3X_'.$extKey.'-'.str_replace('.','_',$extInfo['EM_CONF']['version']).'-z-'.date('YmdHi').'.t3x'; + if (intval($this->CMD['doBackup'])==1) { + header('Content-Type: application/octet-stream'); + header('Content-Disposition: attachment; filename='.$filename); + echo $backUpData; + exit; + } elseif ($this->CMD['dumpTables']) { + $filename='T3X_'.$extKey; + $cTables = count(explode(',',$this->CMD['dumpTables'])); + if ($cTables>1) { + $filename.='-'.$cTables.'tables'; + } else { + $filename.='-'.$this->CMD['dumpTables']; + } + $filename.='+adt.sql'; + + header('Content-Type: application/octet-stream'); + header('Content-Disposition: attachment; filename='.$filename); + echo $this->dumpStaticTables($this->CMD['dumpTables']); + exit; + } else { + $techInfo = $this->makeDetailedExtensionAnalysis($extKey,$extInfo); + $lines=array(); + $lines[] = '' . + $GLOBALS['LANG']->getLL('extBackup_select') . ''; + $lines[]='' . + $GLOBALS['LANG']->getLL('extBackup_files') . '' . + '' . sprintf($GLOBALS['LANG']->getLL('extBackup_download'), + $extKey + ) . '
+ (' . $filename . ',
' . + t3lib_div::formatSize(strlen($backUpData)) . ',
' . + $GLOBALS['LANG']->getLL('extBackup_md5') . ' ' . md5($backUpData) . ') +
'; + + if (is_array($techInfo['tables'])) { + $lines[] = '' . $GLOBALS['LANG']->getLL('extBackup_data_tables') . + '' . $this->extBackup_dumpDataTablesLine($techInfo['tables'], $extKey) . ''; + } + if (is_array($techInfo['static'])) { + $lines[] = '' . $GLOBALS['LANG']->getLL('extBackup_static_tables') . + '' . $this->extBackup_dumpDataTablesLine($techInfo['static'], $extKey) . ''; + } + + $content = ''.implode('',$lines).'
'; + return $content; + } + } else { + throw new RuntimeException( + 'TYPO3 Fatal Error: ' . $GLOBALS['LANG']->getLL('extBackup_unexpected_error'), + 1270853981 + ); + } + } + + /** + * Link to dump of database tables + * + * @param string Extension key + * @param array Extension information array + * @return string HTML + */ + function extBackup_dumpDataTablesLine($tablesArray,$extKey) { + $tables = array(); + $tablesNA = array(); + $allTables = array_keys($GLOBALS['TYPO3_DB']->admin_get_tables()); + + foreach($tablesArray as $tN) { + if (in_array($tN, $allTables)) { + $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('*', $tN); + $tables[$tN] = '  + ' . $tN . '   ' . + sprintf($GLOBALS['LANG']->getLL('extBackup_number_of_records'), + $count) . ''; + } else { + $tablesNA[$tN] = ' ' . $tN . ' ' . + $GLOBALS['LANG']->getLL('extBackup_table_not_there') . ''; + } + } + $label = ''.implode('',array_merge($tables,$tablesNA)).'
';// Candidate for t3lib_div::array_merge() if integer-keys will some day make trouble... + if (count($tables)) { + $label = '' . + $GLOBALS['LANG']->getLL('extBackup_download_all_data') . '

' . $label; + } + else { + $label = $GLOBALS['LANG']->getLL('extBackup_nothing_to_dump') . '

' . $label; + } + return $label; + } + + /** + * Prints a table with extension information in it. + * + * @param string Extension key + * @param array Extension information array + * @param boolean If set, the information array shows information for a remote extension in TER, not a local one. + * @return string HTML content. + */ + function extInformationArray($extKey,$extInfo,$remote=0) { + $lines=array(); + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_general_info') . '' . + $this->helpCol('') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_title') . ' + ' . $extInfo['EM_CONF']['_icon'] . $extInfo['EM_CONF']['title'] . '' . + $this->helpCol('title') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_description') . ' + ' . nl2br(htmlspecialchars($extInfo['EM_CONF']['description'])) . '' . + $this->helpCol('description') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_author') . ' + ' . $this->wrapEmail($extInfo['EM_CONF']['author'] . + ($extInfo['EM_CONF']['author_email'] ? + ' <' . $extInfo['EM_CONF']['author_email'] . '>' : ''), + $extInfo['EM_CONF']['author_email']) . + ($extInfo['EM_CONF']['author_company'] ? + ', ' . $extInfo['EM_CONF']['author_company'] : '') . '' . + $this->helpCol('author') . ''; + + + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_version') . ' + ' . $extInfo['EM_CONF']['version'] . '' . + $this->helpCol('version') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_category') . ' + ' . $this->categories[$extInfo['EM_CONF']['category']] . '' . + $this->helpCol('category') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_state') . ' + ' . $this->states[$extInfo['EM_CONF']['state']] . '' . + $this->helpCol('state') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_shy') . ' + ' . ($extInfo['EM_CONF']['shy'] ? + $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:yes') + : $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:no')) . '' . + $this->helpCol('shy') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_internal') . ' + ' . ($extInfo['EM_CONF']['internal'] ? + $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:yes') + : $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:no')) . '' . + $this->helpCol('internal') . ''; + + + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_depends_on') . ' + ' . $this->depToString($extInfo['EM_CONF']['constraints']) . '' . + $this->helpCol('dependencies') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_conflicts_with') . ' + ' . $this->depToString($extInfo['EM_CONF']['constraints'], 'conflicts') . '' . + $this->helpCol('conflicts') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_suggests') . ' + ' . $this->depToString($extInfo['EM_CONF']['constraints'], 'suggests') . '' . + $this->helpCol('suggests') . ''; + if (!$remote) { + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_priority') . ' + ' . $extInfo['EM_CONF']['priority'] . '' . + $this->helpCol('priority') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_clear_cache') . ' + ' . ($extInfo['EM_CONF']['clearCacheOnLoad'] ? + $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:yes') + : $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:no')) . '' . + $this->helpCol('clearCacheOnLoad') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_incl_modules') . ' + ' . $extInfo['EM_CONF']['module'] . '' . + $this->helpCol('module') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_lock_type') . ' + ' . ($extInfo['EM_CONF']['lockType'] ? + $extInfo['EM_CONF']['lockType'] : '') . '' . + $this->helpCol('lockType') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_load_in_frontend') . ' + ' . ($extInfo['EM_CONF']['doNotLoadInFE'] ? + $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:no') + : $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:yes')) . '' . + $this->helpCol('doNotLoadInFE') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_modifies_tables') . ' + ' . $extInfo['EM_CONF']['modify_tables'] . '' . + $this->helpCol('modify_tables') . ''; + + + // Installation status: + $techInfo = $this->makeDetailedExtensionAnalysis($extKey,$extInfo,1); + $lines[] = ' '.$this->helpCol('').''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_inst_status') . '' . + $this->helpCol('') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_inst_type') . ' + ' . $this->typeLabels[$extInfo['type']] . ' - ' . $this->typeDescr[$extInfo['type']] . '' . + $this->helpCol('type') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_inst_twice') . ' + ' . $this->extInformationArray_dbInst($extInfo['doubleInstall'], $extInfo['type']) . '' . + $this->helpCol('doubleInstall') . ''; + if (is_array($extInfo['files'])) { + sort($extInfo['files']); + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_root_files') . ' + ' . implode('
', $extInfo['files']) . '' . + $this->helpCol('rootfiles') . ''; + } + + if ($techInfo['tables']||$techInfo['static']||$techInfo['fields']) { + if (!$remote && t3lib_extMgm::isLoaded($extKey)) { + $tableStatus = $GLOBALS['TBE_TEMPLATE']->rfw(($techInfo['tables_error'] ? + '' . $GLOBALS['LANG']->getLL('extInfoArray_table_error') . '
' . + $GLOBALS['LANG']->getLL('extInfoArray_missing_fields') : '') . + ($techInfo['static_error'] ? + '' . $GLOBALS['LANG']->getLL('extInfoArray_static_table_error') . '
' . + $GLOBALS['LANG']->getLL('extInfoArray_static_tables_missing_empty') : '')); + } else { + $tableStatus = $techInfo['tables_error']||$techInfo['static_error'] ? + $GLOBALS['LANG']->getLL('extInfoArray_db_update_needed') : $GLOBALS['LANG']->getLL('extInfoArray_tables_ok'); + } + } + + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_db_requirements') . ' + ' . $this->extInformationArray_dbReq($techInfo, 1) . '' . + $this->helpCol('dbReq') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_db_status') . ' + ' . $tableStatus . '' . + $this->helpCol('dbStatus') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_flags') . ' + ' . (is_array($techInfo['flags']) ? + implode('
', $techInfo['flags']) : '') . '' . + $this->helpCol('flags') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_config_template') . ' + ' . ($techInfo['conf'] ? + $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:yes') : '') . '' . + $this->helpCol('conf') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_typoscript_files') . ' + ' . (is_array($techInfo['TSfiles']) ? + implode('
', $techInfo['TSfiles']) : '') . '' . + $this->helpCol('TSfiles') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_language_files') . ' + ' . (is_array($techInfo['locallang']) ? + implode('
', $techInfo['locallang']) : '') . '' . + $this->helpCol('locallang') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_upload_folder') . ' + ' . ($techInfo['uploadfolder'] ? + $techInfo['uploadfolder'] : '') . '' . + $this->helpCol('uploadfolder') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_create_directories') . ' + ' . (is_array($techInfo['createDirs']) ? + implode('
', $techInfo['createDirs']) : '') . '' . + $this->helpCol('createDirs') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_module_names') . ' + ' . (is_array($techInfo['moduleNames']) ? + implode('
', $techInfo['moduleNames']) : '') . '' . + $this->helpCol('moduleNames') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_class_names') . ' + ' . (is_array($techInfo['classes']) ? + implode('
', $techInfo['classes']) : '') . '' . + $this->helpCol('classNames') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_code_warnings') . '
' . + $GLOBALS['LANG']->getLL('extInfoArray_dev_relevant') . ' + ' . (is_array($techInfo['errors']) ? + $GLOBALS['TBE_TEMPLATE']->rfw(implode('
', $techInfo['errors'])) : '') . '' . + $this->helpCol('errors') . ''; + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_annoyances') . '
' . + $GLOBALS['LANG']->getLL('extInfoArray_dev_relevant') . ' + ' . (is_array($techInfo['NSerrors']) ? + (!t3lib_div::inList($this->nameSpaceExceptions, $extKey) ? + t3lib_div::view_array($techInfo['NSerrors']) : + $GLOBALS['TBE_TEMPLATE']->dfw($GLOBALS['LANG']->getLL('extInfoArray_exception'))) : '') . '' . + $this->helpCol('NSerrors') . ''; + + $currentMd5Array = $this->serverExtensionMD5Array($extKey,$extInfo); + $affectedFiles=''; + + $msgLines=array(); + if (strcmp($extInfo['EM_CONF']['_md5_values_when_last_written'],serialize($currentMd5Array))) { + $msgLines[] = $GLOBALS['TBE_TEMPLATE']->rfw('
' . $GLOBALS['LANG']->getLL('extInfoArray_difference_detected') . ''); + $affectedFiles = $this->findMD5ArrayDiff($currentMd5Array,unserialize($extInfo['EM_CONF']['_md5_values_when_last_written'])); + if (count($affectedFiles)) { + $msgLines[] = '
' . $GLOBALS['LANG']->getLL('extInfoArray_modified_files') . '
' . + $GLOBALS['TBE_TEMPLATE']->rfw(implode('
', $affectedFiles)); + } + } + $lines[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_files_changed') . ' + ' . implode('
', $msgLines) . '' . + $this->helpCol('filesChanged') . ''; + } + + return ' + '.implode(' + ',$lines).' +
'; + } + + /** + * Returns HTML with information about database requirements + * + * @param array Technical information array + * @param boolean Table header displayed + * @return string HTML content. + */ + function extInformationArray_dbReq($techInfo,$tableHeader=0) { + return nl2br(trim((is_array($techInfo['tables']) ? + ($tableHeader ? + "\n\n" . $GLOBALS['LANG']->getLL('extDumpTables_tables') . "\n" : '') . + implode(LF, $techInfo['tables']) : '') . + (is_array($techInfo['static']) ? + "\n\n" . $GLOBALS['LANG']->getLL('extBackup_static_tables') . "\n" . + implode(LF, $techInfo['static']) : ''). + (is_array($techInfo['fields']) ? + "\n\n" . $GLOBALS['LANG']->getLL('extInfoArray_additional_fields') . "\n" . + implode('
', $techInfo['fields']) : ''))); + } + + /** + * Double install warning. + * + * @param string Double-install string, eg. "LG" etc. + * @param string Current scope, eg. "L" or "G" or "S" + * @return string Message + */ + function extInformationArray_dbInst($dbInst,$current) { + if (strlen($dbInst)>1) { + $others = array(); + for($a=0;$atypeLabels[substr($dbInst,$a,1)].'"'; + } + } + return $GLOBALS['TBE_TEMPLATE']->rfw( + sprintf($GLOBALS['LANG']->getLL('extInfoArray_double_installation_infotext'), + implode(' ' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:and') . ' ', $others), + $this->typeLabels[$current] + ) + ); + } else return ''; + } + + /** + * Prints the upload form for extensions + * + * @param string Extension key + * @param array Extension information array + * @return string HTML content. + */ + function getRepositoryUploadForm($extKey,$extInfo) { + $content = ' + + + + + + + + + + + + + + + + + + + + + + + +
' . $GLOBALS['LANG']->getLL('repositoryUploadForm_username') . 'doc->formWidth(20).' type="text" name="em[user][fe_u]" value="'.$this->fe_user['username'].'" />
' . $GLOBALS['LANG']->getLL('repositoryUploadForm_password') . 'doc->formWidth(20).' type="password" name="em[user][fe_p]" value="'.$this->fe_user['password'].'" />
' . $GLOBALS['LANG']->getLL('repositoryUploadForm_changelog') . 'doc->formWidth(30,1).' rows="5" name="em[upload][comment]">
' . $GLOBALS['LANG']->getLL('repositoryUploadForm_command') . ' + +
+ +
+ +
+
  +
+ '; + + return $content; + } + + + + + + + + + + + /*********************************** + * + * Extension list rendering + * + **********************************/ + + /** + * Prints the header row for the various listings + * + * @param string Attributes for the tag + * @param array Preset cells in the beginning of the row. Typically a blank cell with a clear-gif + * @param boolean If set, the list is coming from remote server. + * @return string HTML table row + */ + function extensionListRowHeader($trAttrib,$cells,$import=0) { + $cells[] = ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_title') . ''; + + if (!$this->MOD_SETTINGS['display_details']) { + $cells[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_description') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_author') . ''; + } elseif ($this->MOD_SETTINGS['display_details']==2) { + $cells[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_priority') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('listRowHeader_modifies_tables_short') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('listRowHeader_modules') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('listRowHeader_clear_cache_short') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_internal') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_shy') . ''; + } elseif ($this->MOD_SETTINGS['display_details']==3) { + $cells[] = '' . $GLOBALS['LANG']->getLL('listRowHeader_tables_fields') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('listRowHeader_ts_files') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('listRowHeader_affects') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('listRowHeader_modules') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('listRowHeader_config') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_code_warnings') . ''; + } elseif ($this->MOD_SETTINGS['display_details']==4) { + $cells[] = '' . $GLOBALS['LANG']->getLL('listRowHeader_locallang') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('listRowHeader_classes') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_code_warnings') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_annoyances') . ''; + } elseif ($this->MOD_SETTINGS['display_details']==5) { + $cells[] = '' . $GLOBALS['LANG']->getLL('listRowHeader_changed_files') . ''; + } else { + $cells[] = '' . $GLOBALS['LANG']->getLL('listRowHeader_ext_key') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_version') . ''; + if (!$import) { + $cells[] = '' . $GLOBALS['LANG']->getLL('listRowHeader_download_short') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('listRowHeader_documentation_short') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('listRowHeader_type') . ''; + } else { + $cells[] = 'labelInfo($GLOBALS['LANG']->getLL('listRowHeader_title_upload_date')) . '>' . + $GLOBALS['LANG']->getLL('listRowHeader_upload_date') . ''; + $cells[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_author') . ''; + $cells[] = 'labelInfo($GLOBALS['LANG']->getLL('listRowHeader_title_current_version')) . '>' . + $GLOBALS['LANG']->getLL('listRowHeader_current_version') . ''; + $cells[] = 'labelInfo($GLOBALS['LANG']->getLL('listRowHeader_title_current_type')) . '>' . + $GLOBALS['LANG']->getLL('listRowHeader_current_type') . ''; + $cells[] = 'labelInfo($GLOBALS['LANG']->getLL('listRowHeader_title_number_of_downloads')) . '>' . + $GLOBALS['LANG']->getLL('listRowHeader_download_short') . ''; + } + $cells[] = '' . $GLOBALS['LANG']->getLL('extInfoArray_state') . ''; + } + return ' + + '.implode(' + ',$cells).' + '; + } + + /** + * Prints a row with data for the various extension listings + * + * @param string Extension key + * @param array Extension information array + * @param array Preset table cells, eg. install/uninstall icons. + * @param string tag class + * @param array Array with installed extension keys (as keys) + * @param boolean If set, the list is coming from remote server. + * @param string Alternative link URL + * @return string HTML content + */ + function extensionListRow($extKey,$extInfo,$cells,$bgColorClass='',$inst_list=array(),$import=0,$altLinkUrl='') { + + // Icon: + $imgInfo = @getImageSize($this->getExtPath($extKey,$extInfo['type']).'/ext_icon.gif'); + if (is_array($imgInfo)) { + $cells[] = ''; + } elseif ($extInfo['_ICON']) { + $cells[] = ''.$extInfo['_ICON'].''; + } else { + $cells[] = ''; + } + + // Extension title: + $cells[] = '' . t3lib_div::fixed_lgd_cs($extInfo['EM_CONF']['title'] ? htmlspecialchars($extInfo['EM_CONF']['title']) : '' . $extKey . '', 40) . ''; + + // Based on the display mode you will see more or less details: + if (!$this->MOD_SETTINGS['display_details']) { + $cells[] = ''.htmlspecialchars(t3lib_div::fixed_lgd_cs($extInfo['EM_CONF']['description'],400)).'
'; + $cells[] = ''.($extInfo['EM_CONF']['author_email'] ? '' : '').htmlspecialchars($extInfo['EM_CONF']['author']).(htmlspecialchars($extInfo['EM_CONF']['author_email']) ? '' : '').($extInfo['EM_CONF']['author_company'] ? '
'.htmlspecialchars($extInfo['EM_CONF']['author_company']) : '').''; + } elseif ($this->MOD_SETTINGS['display_details']==2) { + $cells[] = ''.$extInfo['EM_CONF']['priority'].''; + $cells[] = ''.implode('
',t3lib_div::trimExplode(',',$extInfo['EM_CONF']['modify_tables'],1)).''; + $cells[] = ''.$extInfo['EM_CONF']['module'].''; + $cells[] = ''.($extInfo['EM_CONF']['clearCacheOnLoad'] ? $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:yes') : '').''; + $cells[] = ''.($extInfo['EM_CONF']['internal'] ? $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:yes') : '').''; + $cells[] = ''.($extInfo['EM_CONF']['shy'] ? $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:yes') : '').''; + } elseif ($this->MOD_SETTINGS['display_details']==3) { + $techInfo = $this->makeDetailedExtensionAnalysis($extKey,$extInfo); + + $cells[] = ''.$this->extInformationArray_dbReq($techInfo). + ''; + $cells[] = ''.(is_array($techInfo['TSfiles']) ? implode('
',$techInfo['TSfiles']) : '').''; + $cells[] = ''.(is_array($techInfo['flags']) ? implode('
',$techInfo['flags']) : '').''; + $cells[] = ''.(is_array($techInfo['moduleNames']) ? implode('
',$techInfo['moduleNames']) : '').''; + $cells[] = ''.($techInfo['conf'] ? $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:yes') : '').''; + $cells[] = ''. + $GLOBALS['TBE_TEMPLATE']->rfw((t3lib_extMgm::isLoaded($extKey) && $techInfo['tables_error'] ? + '' . $GLOBALS['LANG']->getLL('extInfoArray_table_error') . '
' . + $GLOBALS['LANG']->getLL('extInfoArray_missing_fields') : '') . + (t3lib_extMgm::isLoaded($extKey) && $techInfo['static_error'] ? + '' . $GLOBALS['LANG']->getLL('extInfoArray_static_table_error') . '
' . + $GLOBALS['LANG']->getLL('extInfoArray_static_tables_missing_empty') : '')) . + ''; + } elseif ($this->MOD_SETTINGS['display_details']==4) { + $techInfo=$this->makeDetailedExtensionAnalysis($extKey,$extInfo,1); + + $cells[] = ''.(is_array($techInfo['locallang']) ? implode('
',$techInfo['locallang']) : '').''; + $cells[] = ''.(is_array($techInfo['classes']) ? implode('
',$techInfo['classes']) : '').''; + $cells[] = ''.(is_array($techInfo['errors']) ? $GLOBALS['TBE_TEMPLATE']->rfw(implode('
',$techInfo['errors'])) : '').''; + $cells[] = ''.(is_array($techInfo['NSerrors']) ? + (!t3lib_div::inList($this->nameSpaceExceptions, $extKey) ? + t3lib_div::view_array($techInfo['NSerrors']) : + $GLOBALS['TBE_TEMPLATE']->dfw($GLOBALS['LANG']->getLL('extInfoArray_exception'))) : '') . ''; + } elseif ($this->MOD_SETTINGS['display_details']==5) { + $currentMd5Array = $this->serverExtensionMD5Array($extKey,$extInfo); + $affectedFiles = ''; + $msgLines = array(); + $msgLines[] = $GLOBALS['LANG']->getLL('listRow_files') . ' ' . count($currentMd5Array); + if (strcmp($extInfo['EM_CONF']['_md5_values_when_last_written'],serialize($currentMd5Array))) { + $msgLines[] = $GLOBALS['TBE_TEMPLATE']->rfw('
' . $GLOBALS['LANG']->getLL('extInfoArray_difference_detected') . ''); + $affectedFiles = $this->findMD5ArrayDiff($currentMd5Array,unserialize($extInfo['EM_CONF']['_md5_values_when_last_written'])); + if (count($affectedFiles)) { + $msgLines[] = '
' . $GLOBALS['LANG']->getLL('extInfoArray_modified_files') . '
' . + $GLOBALS['TBE_TEMPLATE']->rfw(implode('
', $affectedFiles)); + } + } + $cells[] = ''.implode('
',$msgLines).''; + } else { + // Default view: + $verDiff = $inst_list[$extKey] && $this->versionDifference($extInfo['EM_CONF']['version'],$inst_list[$extKey]['EM_CONF']['version'],$this->versionDiffFactor); + + $cells[] = ''.$extKey.''; + $cells[] = ''.($verDiff ? ''.$GLOBALS['TBE_TEMPLATE']->rfw(htmlspecialchars($extInfo['EM_CONF']['version'])).'' : $extInfo['EM_CONF']['version']).''; + if (!$import) { // Listing extension on LOCAL server: + // Extension Download: + $cells[] = '' . + t3lib_iconWorks::getSpriteIcon('actions-system-extension-download') . + ''; + + // Manual download + $fileP = PATH_site.$this->typePaths[$extInfo['type']].$extKey.'/doc/manual.sxw'; + $cells[] = ''. + ($this->typePaths[$extInfo['type']] && @is_file($fileP) ? + '' . + t3lib_iconWorks::getSpriteIcon('actions-system-extension-documentation') . '' : '') . + ''; + + // Double installation (inclusion of an extension in more than one of system, global or local scopes) + $doubleInstall = ''; + if (strlen($extInfo['doubleInstall']) > 1) { + // Separate the "SL" et al. string into an array and replace L by Local, G by Global etc. + $doubleInstallations = str_replace( + array('S', 'G', 'L'), + array( + $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_tools_em.xml:sysext'), + $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_tools_em.xml:globalext'), + $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_tools_em.xml:localext') + ), + str_split($extInfo['doubleInstall']) + ); + // Last extension is the one actually used + $usedExtension = array_pop($doubleInstallations); + // Next extension is overridden + $overriddenExtensions = array_pop($doubleInstallations); + // If the array is not yet empty, the extension is actually installed 3 times (SGL) + if (count($doubleInstallations) > 0) { + $lastExtension = array_pop($doubleInstallations); + $overriddenExtensions .= ' ' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:and') . ' ' . $lastExtension; + } + $doubleInstallTitle = sprintf( + $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_tools_em.xml:double_inclusion'), + $usedExtension, + $overriddenExtensions + ); + $doubleInstall = ' ' . $GLOBALS['TBE_TEMPLATE']->rfw($extInfo['doubleInstall']) . ''; + } + $cells[] = '' . $this->typeLabels[$extInfo['type']] . $doubleInstall . ''; + } else { // Listing extensions from REMOTE repository: + $inst_curVer = $inst_list[$extKey]['EM_CONF']['version']; + if (isset($inst_list[$extKey])) { + if ($verDiff) $inst_curVer = ''.$GLOBALS['TBE_TEMPLATE']->rfw($inst_curVer).''; + } + $cells[] = '' . t3lib_befunc::date($extInfo['EM_CONF']['lastuploaddate']) . ''; + $cells[] = '' . htmlspecialchars(t3lib_div::fixed_lgd_cs($extInfo['EM_CONF']['author'], $GLOBALS['BE_USER']->uc[titleLen])) . ''; + $cells[] = ''.$inst_curVer.''; + $cells[] = ''.$this->typeLabels[$inst_list[$extKey]['type']].(strlen($inst_list[$extKey]['doubleInstall'])>1?' '.$GLOBALS['TBE_TEMPLATE']->rfw($inst_list[$extKey]['doubleInstall']).'':'').''; + $cells[] = ''.($extInfo['downloadcounter_all']?$extInfo['downloadcounter_all']:'  ').'/'.($extInfo['downloadcounter']?$extInfo['downloadcounter']:' ').''; + } + $cells[] = ''.$this->states[$extInfo['EM_CONF']['state']].''; + } + + // show a different background through a different class for insecure (-1) extensions, + // for unreviewed (0) an reviewed extensions (1), just use the regular class + if ($this->xmlhandler->getReviewState($extKey,$extInfo['EM_CONF']['version']) < 0) { + $bgclass = ' class="unsupported-ext"'; + } else { + $bgclass = ' class="' . ($bgColorClass ? $bgColorClass : 'em-listbg1') . '"'; + } + + return ' + + '.implode(' + ',$cells).' + '; + } + + + + + + + /************************************ + * + * Output helper functions + * + ************************************/ + + /** + * Wrapping input string in a link tag with link to email address + * + * @param string Input string, being wrapped in tags + * @param string Email address for use in link. + * @return string Output + */ + function wrapEmail($str,$email) { + if ($email) { + $str = ''.htmlspecialchars($str).''; + } + return $str; + } + + /** + * Returns help text if applicable. + * + * @param string Help text key + * @return string HTML table cell + */ + function helpCol($key) { + global $BE_USER; + if ($BE_USER->uc['edit_showFieldHelp']) { + if (empty($key)) { + return ' '; + } + else { + return t3lib_BEfunc::cshItem($this->descrTable, 'emconf_'.$key, $GLOBALS['BACK_PATH'], '|'); + } + } + else { + return ''; + } + } + + /** + * Returns title and style attribute for mouseover help text. + * + * @param string Help text. + * @return string title="" attribute prepended with a single space + */ + function labelInfo($str) { + return ' title="'.htmlspecialchars($str).'" style="cursor:help;"'; + } + + /** + * Returns a header for an extensions including icon if any + * + * @param string Extension key + * @param array Extension information array + * @param string align-attribute value (for tag) + * @return string HTML; Extension title and image. + */ + function extensionTitleIconHeader($extKey,$extInfo,$align='top') { + $imgInfo = @getImageSize($this->getExtPath($extKey,$extInfo['type']).'/ext_icon.gif'); + $out = ''; + if (is_array($imgInfo)) { + $out.= ''; + } + $out.= $extInfo['EM_CONF']['title'] ? htmlspecialchars(t3lib_div::fixed_lgd_cs($extInfo['EM_CONF']['title'],40)) : ''.$extKey.''; + return $out; + } + + /** + * Returns image tag for "uninstall" + * + * @return string tag + */ + function removeButton() { + return t3lib_iconWorks::getSpriteIcon('actions-system-extension-uninstall', array('title' => $GLOBALS['LANG']->getLL('ext_details_remove_ext'))); + } + + /** + * Returns image for "install" + * + * @return string tag + */ + function installButton() { + return t3lib_iconWorks::getSpriteIcon('actions-system-extension-install', array('title' => $GLOBALS['LANG']->getLL('helperFunction_install_extension'))); + } + + /** + * Warning ( + text string) message about the impossibility to import extensions (both local and global locations are disabled...) + * + * @return string + text string. + */ + function noImportMsg() { + return t3lib_iconWorks::getSpriteIcon('status-dialog-warning') . + '' . $GLOBALS['LANG']->getLL('helperFunction_import_not_possible') . ''; + } + + /** + * Checks whether the passed dependency is TER2-style (array) and returns a single string for displaying the dependencies. + * + * It leaves out all version numbers and the "php" and "typo3" dependencies, as they are implicit and of no interest without the version number. + * + * @param mixed $dep Either a string or an array listing dependencies. + * @param string $type The dependency type to list if $dep is an array + * @return string A simple dependency list for display + */ + function depToString($dep,$type='depends') { + if(is_array($dep)) { + unset($dep[$type]['php']); + unset($dep[$type]['typo3']); + $s = (count($dep[$type])) ? implode(',', array_keys($dep[$type])) : ''; + return $s; + } + return ''; + } + + /** + * Checks whether the passed dependency is TER-style (string) or TER2-style (array) and returns a single string for displaying the dependencies. + * + * It leaves out all version numbers and the "php" and "typo3" dependencies, as they are implicit and of no interest without the version number. + * + * @param mixed $dep Either a string or an array listing dependencies. + * @param string $type The dependency type to list if $dep is an array + * @return string A simple dependency list for display + */ + function stringToDep($dep) { + $constraint = array(); + if(is_string($dep) && strlen($dep)) { + $dep = explode(',',$dep); + foreach($dep as $v) { + $constraint[$v] = ''; + } + } + return $constraint; + } + + + + + + + + + /******************************** + * + * Read information about all available extensions + * + *******************************/ + + /** + * Returns the list of available (installed) extensions + * + * @return array Array with two arrays, list array (all extensions with info) and category index + * @see getInstExtList() + */ + function getInstalledExtensions() { + $list = array(); + $cat = $this->defaultCategories; + + $path = PATH_typo3.'sysext/'; + $this->getInstExtList($path,$list,$cat,'S'); + + $path = PATH_typo3.'ext/'; + $this->getInstExtList($path,$list,$cat,'G'); + + $path = PATH_typo3conf.'ext/'; + $this->getInstExtList($path,$list,$cat,'L'); + + return array($list,$cat); + } + + /** + * Gathers all extensions in $path + * + * @param string Absolute path to local, global or system extensions + * @param array Array with information for each extension key found. Notice: passed by reference + * @param array Categories index: Contains extension titles grouped by various criteria. + * @param string Path-type: L, G or S + * @return void "Returns" content by reference + * @access private + * @see getInstalledExtensions() + */ + function getInstExtList($path,&$list,&$cat,$type) { + + if (@is_dir($path)) { + $extList = t3lib_div::get_dirs($path); + if (is_array($extList)) { + foreach($extList as $extKey) { + if (@is_file($path.$extKey.'/ext_emconf.php')) { + $emConf = $this->includeEMCONF($path.$extKey.'/ext_emconf.php', $extKey); + if (is_array($emConf)) { + if (is_array($list[$extKey])) { + $list[$extKey]=array('doubleInstall'=>$list[$extKey]['doubleInstall']); + } + $list[$extKey]['doubleInstall'].= $type; + $list[$extKey]['type'] = $type; + $list[$extKey]['EM_CONF'] = $emConf; + $list[$extKey]['files'] = t3lib_div::getFilesInDir($path.$extKey, '', 0, '', $this->excludeForPackaging); + + $this->setCat($cat,$list[$extKey], $extKey); + } + } + } + } + } + } + + /** + * Fixes an old style ext_emconf.php array by adding constraints if needed and removing deprecated keys + * + * @param array $emConf + * @return array + */ + function fixEMCONF($emConf) { + if(!isset($emConf['constraints']) || !isset($emConf['constraints']['depends']) || !isset($emConf['constraints']['conflicts']) || !isset($emConf['constraints']['suggests'])) { + if(!isset($emConf['constraints']) || !isset($emConf['constraints']['depends'])) { + $emConf['constraints']['depends'] = $this->stringToDep($emConf['dependencies']); + if(strlen($emConf['PHP_version'])) { + $versionRange = $this->splitVersionRange($emConf['PHP_version']); + if (version_compare($versionRange[0],'3.0.0','<')) $versionRange[0] = '3.0.0'; + if (version_compare($versionRange[1],'3.0.0','<')) $versionRange[1] = '0.0.0'; + $emConf['constraints']['depends']['php'] = implode('-',$versionRange); + } + if(strlen($emConf['TYPO3_version'])) { + $versionRange = $this->splitVersionRange($emConf['TYPO3_version']); + if (version_compare($versionRange[0],'3.5.0','<')) $versionRange[0] = '3.5.0'; + if (version_compare($versionRange[1],'3.5.0','<')) $versionRange[1] = '0.0.0'; + $emConf['constraints']['depends']['typo3'] = implode('-',$versionRange); + } + } + if(!isset($emConf['constraints']) || !isset($emConf['constraints']['conflicts'])) { + $emConf['constraints']['conflicts'] = $this->stringToDep($emConf['conflicts']); + } + if(!isset($emConf['constraints']) || !isset($emConf['constraints']['suggests'])) { + $emConf['constraints']['suggests'] = array(); + } + } elseif (isset($emConf['constraints']) && isset($emConf['dependencies'])) { + $emConf['suggests'] = isset($emConf['suggests']) ? $emConf['suggests'] : array(); + $emConf['dependencies'] = $this->depToString($emConf['constraints']); + $emConf['conflicts'] = $this->depToString($emConf['constraints'], 'conflicts'); + } + + // sanity check for version numbers, intentionally only checks php and typo3 + if(isset($emConf['constraints']['depends']) && isset($emConf['constraints']['depends']['php'])) { + $versionRange = $this->splitVersionRange($emConf['constraints']['depends']['php']); + if (version_compare($versionRange[0],'3.0.0','<')) $versionRange[0] = '3.0.0'; + if (version_compare($versionRange[1],'3.0.0','<')) $versionRange[1] = '0.0.0'; + $emConf['constraints']['depends']['php'] = implode('-',$versionRange); + } + if(isset($emConf['constraints']['depends']) && isset($emConf['constraints']['depends']['typo3'])) { + $versionRange = $this->splitVersionRange($emConf['constraints']['depends']['typo3']); + if (version_compare($versionRange[0],'3.5.0','<')) $versionRange[0] = '3.5.0'; + if (version_compare($versionRange[1],'3.5.0','<')) $versionRange[1] = '0.0.0'; + $emConf['constraints']['depends']['typo3'] = implode('-',$versionRange); + } + + unset($emConf['private']); + unset($emConf['download_password']); + unset($emConf['TYPO3_version']); + unset($emConf['PHP_version']); + + return $emConf; + } + + /** + * Splits a version range into an array. + * + * If a single version number is given, it is considered a minimum value. + * If a dash is found, the numbers left and right are considered as minimum and maximum. Empty values are allowed. + * + * @param string $ver A string with a version range. + * @return array + */ + function splitVersionRange($ver) { + $versionRange = array(); + if (strstr($ver, '-')) { + $versionRange = explode('-', $ver, 2); + } else { + $versionRange[0] = $ver; + $versionRange[1] = ''; + } + + if (!$versionRange[0]) { $versionRange[0] = '0.0.0'; } + if (!$versionRange[1]) { $versionRange[1] = '0.0.0'; } + + return $versionRange; + } + + /** + * Maps remote extensions information into $cat/$list arrays for listing + * + * @param boolean If set the info in the internal extensionsXML array will be unset before returning the result. + * @return array List array and category index as key 0 / 1 in an array. + */ + function prepareImportExtList($unsetProc = false) { + $list = array(); + $cat = $this->defaultCategories; + $filepath = $this->getMirrorURL(); + + foreach ($this->xmlhandler->extensionsXML as $extKey => $data) { + $GLOBALS['LANG']->csConvObj->convArray($data,'utf-8',$GLOBALS['LANG']->charSet); // is there a better place for conversion? + $list[$extKey]['type'] = '_'; + $version = array_keys($data['versions']); + $extPath = t3lib_div::strtolower($extKey); + $list[$extKey]['_ICON'] = ''; + $list[$extKey]['downloadcounter'] = $data['downloadcounter']; + + foreach(array_keys($data['versions']) as $version) { + $list[$extKey]['versions'][$version]['downloadcounter'] = $data['versions'][$version]['downloadcounter']; + + $list[$extKey]['versions'][$version]['EM_CONF'] = array( + 'version' => $version, + 'title' => $data['versions'][$version]['title'], + 'description' => $data['versions'][$version]['description'], + 'category' => $data['versions'][$version]['category'], + 'constraints' => $data['versions'][$version]['dependencies'], + 'state' => $data['versions'][$version]['state'], + 'reviewstate' => $data['versions'][$version]['reviewstate'], + 'lastuploaddate' => $data['versions'][$version]['lastuploaddate'], + 'author' => $data['versions'][$version]['authorname'], + 'author_email' => $data['versions'][$version]['authoremail'], + 'author_company' => $data['versions'][$version]['authorcompany'], + ); + } + $this->setCat($cat, $list[$extKey]['versions'][$version], $extKey); + if ($unsetProc) { + unset($this->xmlhandler->extensionsXML[$extKey]); + } + } + + return array($list,$cat); + } + + /** + * Set category array entries for extension + * + * @param array Category index array + * @param array Part of list array for extension. + * @param string Extension key + * @return array Modified category index array + */ + function setCat(&$cat,$listArrayPart,$extKey) { + + // Getting extension title: + $extTitle = $listArrayPart['EM_CONF']['title']; + + // Category index: + $index = $listArrayPart['EM_CONF']['category']; + $cat['cat'][$index][$extKey] = $extTitle; + + // Author index: + $index = $listArrayPart['EM_CONF']['author'].($listArrayPart['EM_CONF']['author_company']?', '.$listArrayPart['EM_CONF']['author_company']:''); + $cat['author_company'][$index][$extKey] = $extTitle; + + // State index: + $index = $listArrayPart['EM_CONF']['state']; + $cat['state'][$index][$extKey] = $extTitle; + + // Type index: + $index = $listArrayPart['type']; + $cat['type'][$index][$extKey] = $extTitle; + + // Return categories: + return $cat; + } + + + + + + + + + + + /******************************* + * + * Extension analyzing (detailed information) + * + ******************************/ + + /** + * Perform a detailed, technical analysis of the available extension on server! + * Includes all kinds of verifications + * Takes some time to process, therfore use with care, in particular in listings. + * + * @param string Extension key + * @param array Extension information + * @param boolean If set, checks for validity of classes etc. + * @return array Information in an array. + */ + function makeDetailedExtensionAnalysis($extKey,$extInfo,$validity=0) { + + // Get absolute path of the extension + $absPath = $this->getExtPath($extKey,$extInfo['type']); + + $infoArray = array(); + + $table_class_prefix = substr($extKey,0,5)=='user_' ? 'user_' : 'tx_'.str_replace('_','',$extKey).'_'; + $module_prefix = substr($extKey,0,5)=='user_' ? 'u' : 'tx'.str_replace('_','',$extKey); + + // Database status: + $dbInfo = $this->checkDBupdates($extKey,$extInfo,1); + + // Database structure required: + if (is_array($dbInfo['structure']['tables_fields'])) { + $modify_tables = t3lib_div::trimExplode(',',$extInfo['EM_CONF']['modify_tables'],1); + $infoArray['dump_tf'] = array(); + + foreach($dbInfo['structure']['tables_fields'] as $tN => $d) { + if (in_array($tN,$modify_tables)) { + $infoArray['fields'][] = $tN.': '. + (is_array($d['fields']) ? implode(', ',array_keys($d['fields'])) : ''). + (is_array($d['keys']) ? + ' + ' . count($d['keys']) . ' ' . $GLOBALS['LANG']->getLL('detailedExtAnalysis_keys') : '') . + ''; + if (is_array($d['fields'])) { + foreach ($d['fields'] as $fN => $value) { + $infoArray['dump_tf'][] = $tN.'.'.$fN; + if (!t3lib_div::isFirstPartOfStr($fN,$table_class_prefix)) { + $infoArray['NSerrors']['fields'][$fN] = $fN; + } else { + $infoArray['NSok']['fields'][$fN] = $fN; + } + } + } + if (is_array($d['keys'])) { + foreach ($d['keys'] as $fN => $value) { + $infoArray['dump_tf'][] = $tN.'.KEY:'.$fN; + } + } + } else { + $infoArray['dump_tf'][] = $tN; + $infoArray['tables'][] = $tN; + if (!t3lib_div::isFirstPartOfStr($tN,$table_class_prefix)) { + $infoArray['NSerrors']['tables'][$tN] = $tN; + } else $infoArray['NSok']['tables'][$tN] = $tN; + } + } + if (count($dbInfo['structure']['diff']['diff']) || count($dbInfo['structure']['diff']['extra'])) { + $msg = array(); + if (count($dbInfo['structure']['diff']['diff'])) { + $msg[] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_tables_are_missing'); + } + if (count($dbInfo['structure']['diff']['extra'])) { + $msg[] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_tables_are_of_wrong_type'); + } + $infoArray['tables_error'] = 1; + if (t3lib_extMgm::isLoaded($extKey)) { + $infoArray['errors'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_tables_are'), + implode(' ' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:and') . ' ', $msg) + ); + } + } + } + + // Static tables? + if (is_array($dbInfo['static'])) { + $infoArray['static'] = array_keys($dbInfo['static']); + + foreach($dbInfo['static'] as $tN => $d) { + if (!$d['exists']) { + $infoArray['static_error'] = 1; + if (t3lib_extMgm::isLoaded($extKey)) { + $infoArray['errors'][] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_static_tables_missing'); + } + if (!t3lib_div::isFirstPartOfStr($tN,$table_class_prefix)) { + $infoArray['NSerrors']['tables'][$tN] = $tN; + } else $infoArray['NSok']['tables'][$tN] = $tN; + } + } + } + + // Backend Module-check: + $knownModuleList = t3lib_div::trimExplode(',',$extInfo['EM_CONF']['module'],1); + foreach($knownModuleList as $mod) { + if (@is_dir($absPath.$mod)) { + if (@is_file($absPath.$mod.'/conf.php')) { + $confFileInfo = $this->modConfFileAnalysis($absPath.$mod.'/conf.php'); + if (is_array($confFileInfo['TYPO3_MOD_PATH'])) { + $shouldBePath = $this->typeRelPaths[$extInfo['type']].$extKey.'/'.$mod.'/'; + if (strcmp($confFileInfo['TYPO3_MOD_PATH'][1][1],$shouldBePath)) { + $infoArray['errors'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_wrong_mod_path'), + $confFileInfo['TYPO3_MOD_PATH'][1][1], + $shouldBePath + ); + } + } else { + // It seems like TYPO3_MOD_PATH and therefore also this warning is no longer needed. + // $infoArray['errors'][] = 'No definition of TYPO3_MOD_PATH constant found inside!'; + } + if (is_array($confFileInfo['MCONF_name'])) { + $mName = $confFileInfo['MCONF_name'][1][1]; + $mNameParts = explode('_',$mName); + $infoArray['moduleNames'][] = $mName; + if (!t3lib_div::isFirstPartOfStr($mNameParts[0],$module_prefix) && + (!$mNameParts[1] || !t3lib_div::isFirstPartOfStr($mNameParts[1],$module_prefix))) { + $infoArray['NSerrors']['modname'][] = $mName; + } else $infoArray['NSok']['modname'][] = $mName; + } else $infoArray['errors'][] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_mconf_missing'); + } else $infoArray['errors'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_be_module_conf_missing'), + $mod . '/conf.php' + ); + } else $infoArray['errors'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_module_folder_missing'), + $mod . '/' + ); + } + $dirs = t3lib_div::get_dirs($absPath); + if (is_array($dirs)) { + reset($dirs); + while(list(,$mod) = each($dirs)) { + if (!in_array($mod,$knownModuleList) && @is_file($absPath.$mod.'/conf.php')) { + $confFileInfo = $this->modConfFileAnalysis($absPath.$mod.'/conf.php'); + if (is_array($confFileInfo)) { + $infoArray['errors'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_unconfigured_module'), + $mod . '/conf.php' + ); + } + } + } + } + + // ext_tables.php: + if (@is_file($absPath.'ext_tables.php')) { + $content = t3lib_div::getUrl($absPath.'ext_tables.php'); + if (stristr($content, 't3lib_extMgm::addModule')) $infoArray['flags'][] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_module'); + if (stristr($content, 't3lib_extMgm::insertModuleFunction')) $infoArray['flags'][] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_module_and_more'); + if (stristr($content, 't3lib_div::loadTCA')) $infoArray['flags'][] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_loadTCA'); + if (stristr($content, '$TCA[')) $infoArray['flags'][] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_TCA'); + if (stristr($content, 't3lib_extMgm::addPlugin')) $infoArray['flags'][] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_plugin'); + } + + // ext_localconf.php: + if (@is_file($absPath.'ext_localconf.php')) { + $content = t3lib_div::getUrl($absPath.'ext_localconf.php'); + if (stristr($content, 't3lib_extMgm::addPItoST43')) $infoArray['flags'][] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_plugin_st43'); + if (stristr($content, 't3lib_extMgm::addPageTSConfig')) $infoArray['flags'][] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_page_ts'); + if (stristr($content, 't3lib_extMgm::addUserTSConfig')) $infoArray['flags'][] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_user_ts'); + if (stristr($content, 't3lib_extMgm::addTypoScriptSetup')) $infoArray['flags'][] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_ts_setup'); + if (stristr($content, 't3lib_extMgm::addTypoScriptConstants')) $infoArray['flags'][] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_ts_constants'); + } + + if (@is_file($absPath.'ext_typoscript_constants.txt')) { + $infoArray['TSfiles'][] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_constants'); + } + if (@is_file($absPath.'ext_typoscript_setup.txt')) { + $infoArray['TSfiles'][] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_setup'); + } + if (@is_file($absPath.'ext_conf_template.txt')) { + $infoArray['conf'] = 1; + } + + // Classes: + if ($validity) { + $filesInside = $this->getClassIndexLocallangFiles($absPath,$table_class_prefix,$extKey); + if (is_array($filesInside['errors'])) $infoArray['errors'] = array_merge((array)$infoArray['errors'],$filesInside['errors']); + if (is_array($filesInside['NSerrors'])) $infoArray['NSerrors'] = array_merge((array)$infoArray['NSerrors'],$filesInside['NSerrors']); + if (is_array($filesInside['NSok'])) $infoArray['NSok'] = array_merge((array)$infoArray['NSok'],$filesInside['NSok']); + $infoArray['locallang'] = $filesInside['locallang']; + $infoArray['classes'] = $filesInside['classes']; + } + + // Upload folders + if ($extInfo['EM_CONF']['uploadfolder']) { + $infoArray['uploadfolder'] = $this->ulFolder($extKey); + if (!@is_dir(PATH_site.$infoArray['uploadfolder'])) { + $infoArray['errors'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_no_upload_folder'), + $infoArray['uploadfolder'] + ); + $infoArray['uploadfolder'] = ''; + } + } + + // Create directories: + if ($extInfo['EM_CONF']['createDirs']) { + $infoArray['createDirs'] = array_unique(t3lib_div::trimExplode(',',$extInfo['EM_CONF']['createDirs'],1)); + foreach($infoArray['createDirs'] as $crDir) { + if (!@is_dir(PATH_site.$crDir)) { + $infoArray['errors'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_no_upload_folder'), + $crDir + ); + } + } + } + + // Return result array: + return $infoArray; + } + + /** + * Analyses the php-scripts of an available extension on server + * + * @param string Absolute path to extension + * @param string Prefix for tables/classes. + * @param string Extension key + * @return array Information array. + * @see makeDetailedExtensionAnalysis() + */ + function getClassIndexLocallangFiles($absPath,$table_class_prefix,$extKey) { + $filesInside = t3lib_div::removePrefixPathFromList(t3lib_div::getAllFilesAndFoldersInPath(array(),$absPath,'php,inc',0,99,$this->excludeForPackaging),$absPath); + $out = array(); + $reg = array(); + + foreach($filesInside as $fileName) { + if (substr($fileName,0,4)!='ext_' && substr($fileName,0,6)!='tests/') { // ignore supposed-to-be unit tests as well + $baseName = basename($fileName); + if (substr($baseName,0,9)=='locallang' && substr($baseName,-4)=='.php') { + $out['locallang'][] = $fileName; + } elseif ($baseName!='conf.php') { + if (filesize($absPath.$fileName)<500*1024) { + $fContent = t3lib_div::getUrl($absPath.$fileName); + unset($reg); + if (preg_match('/\n[[:space:]]*class[[:space:]]*([[:alnum:]_]+)([[:alnum:][:space:]_]*)/',$fContent,$reg)) { + + // Find classes: + $lines = explode(LF,$fContent); + foreach($lines as $l) { + $line = trim($l); + unset($reg); + if (preg_match('/^class[[:space:]]*([[:alnum:]_]+)([[:alnum:][:space:]_]*)/',$line,$reg)) { + $out['classes'][] = $reg[1]; + $out['files'][$fileName]['classes'][] = $reg[1]; + if ($reg[1]!=='ext_update' && substr($reg[1],0,3)!='ux_' && !t3lib_div::isFirstPartOfStr($reg[1],$table_class_prefix) && strcmp(substr($table_class_prefix,0,-1),$reg[1])) { + $out['NSerrors']['classname'][] = $reg[1]; + } else $out['NSok']['classname'][] = $reg[1]; + } + } + // If class file prefixed 'class.'.... + if (substr($baseName,0,6)=='class.') { + $fI = pathinfo($baseName); + $testName=substr($baseName,6,-(1+strlen($fI['extension']))); + if ($testName!=='ext_update' && substr($testName,0,3)!='ux_' && !t3lib_div::isFirstPartOfStr($testName,$table_class_prefix) && strcmp(substr($table_class_prefix,0,-1),$testName)) { + $out['NSerrors']['classfilename'][] = $baseName; + } else { + $out['NSok']['classfilename'][] = $baseName; + if (is_array($out['files'][$fileName]['classes']) && $this->first_in_array($testName,$out['files'][$fileName]['classes'],1)) { + $out['msg'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_class_ok'), + $fileName, $testName + ); + } else $out['errors'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_class_not_ok'), + $fileName, $testName + ); + } + } + + // Check for proper XCLASS definition + // Match $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS'] with single or doublequotes + $XclassSearch = '\$TYPO3_CONF_VARS\[TYPO3_MODE\]\[[\'"]XCLASS[\'"]\]'; + $XclassParts = preg_split('/if \(defined\([\'"]TYPO3_MODE[\'"]\) && ' . $XclassSearch . '/', $fContent, 2); + if (count($XclassParts) !== 2) { + // Match $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS'] with single or doublequotes + $XclassSearch = '\$GLOBALS\[[\'"]TYPO3_CONF_VARS[\'"]\]\[TYPO3_MODE\]\[[\'"]XCLASS[\'"]\]'; + $XclassParts = preg_split('/if \(defined\([\'"]TYPO3_MODE[\'"]\) && ' . $XclassSearch . '/', $fContent, 2); + } + if (count($XclassParts)==2) { + unset($reg); + preg_match('/^\[[\'"]([[:alnum:]_\/\.]*)[\'"]\]/',$XclassParts[1],$reg); + if ($reg[1]) { + $cmpF = 'ext/'.$extKey.'/'.$fileName; + if (!strcmp($reg[1],$cmpF)) { + if (preg_match('/_once[[:space:]]*\(' . $XclassSearch . '\[[\'"]' . preg_quote($cmpF, '/') . '[\'"]\]\);/', $XclassParts[1])) { + $out['msg'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_xclass_ok'), $fileName); + } else $out['errors'][] = $GLOBALS['LANG']->getLL('detailedExtAnalysis_xclass_no_include'); + } else $out['errors'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_xclass_incorrect'), + $reg[1], $cmpF + ); + } else $out['errors'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_no_xclass_filename'), $fileName); + } elseif (!$this->first_in_array('ux_', $out['files'][$fileName]['classes'])) { + // No Xclass definition required if classname starts with 'ux_' + $out['errors'][] = sprintf($GLOBALS['LANG']->getLL('detailedExtAnalysis_no_xclass_found'), $fileName); + } + } + } + } + } + } + return $out; + } + + /** + * Reads $confFilePath (a module $conf-file) and returns information on the existence of TYPO3_MOD_PATH definition and MCONF_name + * + * @param string Absolute path to a "conf.php" file of a module which we are analysing. + * @return array Information found. + * @see writeTYPO3_MOD_PATH() + */ + function modConfFileAnalysis($confFilePath) { + $lines = explode(LF,t3lib_div::getUrl($confFilePath)); + $confFileInfo = array(); + $confFileInfo['lines'] = $lines; + $reg = array(); + + foreach($lines as $k => $l) { + $line = trim($l); + + unset($reg); + if (preg_match('/^define[[:space:]]*\([[:space:]]*["\']TYPO3_MOD_PATH["\'][[:space:]]*,[[:space:]]*["\']([[:alnum:]_\/\.]+)["\'][[:space:]]*\)[[:space:]]*;/',$line,$reg)) { + $confFileInfo['TYPO3_MOD_PATH'] = array($k,$reg); + } + + unset($reg); + if (preg_match('/^\$MCONF\[["\']?name["\']?\][[:space:]]*=[[:space:]]*["\']([[:alnum:]_]+)["\'];/',$line,$reg)) { + $confFileInfo['MCONF_name'] = array($k,$reg); + } + } + return $confFileInfo; + } + + /** + * Creates a MD5-hash array over the current files in the extension + * + * @param string Extension key + * @param array Extension information array + * @return array MD5-keys + */ + function serverExtensionMD5Array($extKey,$conf) { + + // Creates upload-array - including filelist. + $mUA = $this->makeUploadArray($extKey,$conf); + + $md5Array = array(); + if (is_array($mUA['FILES'])) { + + // Traverse files. + foreach($mUA['FILES'] as $fN => $d) { + if ($fN!='ext_emconf.php') { + $md5Array[$fN] = substr($d['content_md5'],0,4); + } + } + } else debug($mUA); + return $md5Array; + } + + /** + * Compares two arrays with MD5-hash values for analysis of which files has changed. + * + * @param array Current values + * @param array Past values + * @return array Affected files + */ + function findMD5ArrayDiff($current,$past) { + if (!is_array($current)) $current = array(); + if (!is_array($past)) $past = array(); + $filesInCommon = array_intersect($current,$past); + $diff1 = array_keys(array_diff($past,$filesInCommon)); + $diff2 = array_keys(array_diff($current,$filesInCommon)); + $affectedFiles = array_unique(array_merge($diff1,$diff2)); + return $affectedFiles; + } + + + + + + + + + + + /*********************************** + * + * File system operations + * + **********************************/ + + /** + * Creates directories in $extDirPath + * + * @param array Array of directories to create relative to extDirPath, eg. "blabla", "blabla/blabla" etc... + * @param string Absolute path to directory. + * @return mixed Returns false on success or an error string + */ + function createDirsInPath($dirs,$extDirPath) { + if (is_array($dirs)) { + foreach($dirs as $dir) { + $error = t3lib_div::mkdir_deep($extDirPath,$dir); + if ($error) return $error; + } + } + + return false; + } + + /** + * Removes the extension directory (including content) + * + * @param string Extension directory to remove (with trailing slash) + * @param boolean If set, will leave the extension directory + * @return boolean False on success, otherwise error string. + */ + function removeExtDirectory($removePath,$removeContentOnly=0) { + $errors = array(); + if (@is_dir($removePath) && substr($removePath,-1)=='/' && ( + t3lib_div::isFirstPartOfStr($removePath,PATH_site.$this->typePaths['G']) || + t3lib_div::isFirstPartOfStr($removePath,PATH_site.$this->typePaths['L']) || + (t3lib_div::isFirstPartOfStr($removePath,PATH_site.$this->typePaths['S']) && $this->systemInstall) || + t3lib_div::isFirstPartOfStr($removePath, PATH_site . $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] . '_temp_/')) // Playing-around directory... + ) { + + // All files in extension directory: + $fileArr = t3lib_div::getAllFilesAndFoldersInPath(array(),$removePath,'',1); + if (is_array($fileArr)) { + + // Remove files in dirs: + foreach($fileArr as $removeFile) { + if (!@is_dir($removeFile)) { + if (@is_file($removeFile) && t3lib_div::isFirstPartOfStr($removeFile,$removePath) && strcmp($removeFile,$removePath)) { // ... we are very paranoid, so we check what cannot go wrong: that the file is in fact within the prefix path! + @unlink($removeFile); + clearstatcache(); + if (@is_file($removeFile)) { + $errors[] = sprintf($GLOBALS['LANG']->getLL('rmExtDir_could_not_be_deleted'), + $removeFile + ); + } + } else $errors[] = sprintf($GLOBALS['LANG']->getLL('rmExtDir_error_file'), + $removeFile, $removePath + ); + } + } + + // Remove directories: + $remDirs = $this->extractDirsFromFileList(t3lib_div::removePrefixPathFromList($fileArr,$removePath)); + $remDirs = array_reverse($remDirs); // Must delete outer directories first... + foreach($remDirs as $removeRelDir) { + $removeDir = $removePath.$removeRelDir; + if (@is_dir($removeDir)) { + @rmdir($removeDir); + clearstatcache(); + if (@is_dir($removeDir)) { + $errors[] = sprintf($GLOBALS['LANG']->getLL('rmExtDir_error_files_left'), + $removeDir + ); + } + } else $errors[] = sprintf($GLOBALS['LANG']->getLL('rmExtDir_error_no_dir'), + $removeDir + ); + } + + // If extension dir should also be removed: + if (!$removeContentOnly) { + @rmdir($removePath); + clearstatcache(); + if (@is_dir($removePath)) { + $errors[] = sprintf($GLOBALS['LANG']->getLL('rmExtDir_error_folders_left'), + $removePath + ); + } + } + } else $errors[] = $GLOBALS['LANG']->getLL('rmExtDir_error') . ' ' . $fileArr; + } else $errors[] = $GLOBALS['LANG']->getLL('rmExtDir_error_unallowed_path') . ' ' . $removePath; + + // Return errors if any: + return implode(LF,$errors); + } + + /** + * Removes the current extension of $type and creates the base folder for the new one (which is going to be imported) + * + * @param array Data for imported extension + * @param string Extension installation scope (L,G,S) + * @param boolean If set, nothing will be deleted (neither directory nor files) + * @return mixed Returns array on success (with extension directory), otherwise an error string. + */ + function clearAndMakeExtensionDir($importedData,$type,$dontDelete=0) { + if (!$importedData['extKey']) return $GLOBALS['LANG']->getLL('clearMakeExtDir_no_ext_key'); + + // Setting install path (L, G, S or fileadmin/_temp_/) + $path = ''; + switch((string)$type) { + case 'G': + case 'L': + $path = PATH_site.$this->typePaths[$type]; + $suffix = ''; + + // Creates the typo3conf/ext/ directory if it does NOT already exist: + if ((string)$type=='L' && !@is_dir($path)) { + t3lib_div::mkdir($path); + } + break; + default: + if ($this->systemInstall && (string)$type=='S') { + $path = PATH_site.$this->typePaths[$type]; + $suffix = ''; + } else { + $path = PATH_site . $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] . '_temp_/'; + $suffix = '_'.date('dmy-His'); + } + break; + } + + // If the install path is OK... + if ($path && @is_dir($path)) { + + // Set extension directory: + $extDirPath = $path.$importedData['extKey'].$suffix.'/'; + + // Install dir was found, remove it then: + if (@is_dir($extDirPath)) { + if($dontDelete) return array($extDirPath); + $res = $this->removeExtDirectory($extDirPath); + if ($res) { + $flashMessage = t3lib_div::makeInstance( + 't3lib_FlashMessage', + nl2br($res), + sprintf($GLOBALS['LANG']->getLL('clearMakeExtDir_could_not_remove_dir'), $extDirPath), + t3lib_FlashMessage::ERROR + ); + return $flashMessage->render(); + } + } + + // We go create... + t3lib_div::mkdir($extDirPath); + if (!is_dir($extDirPath)) { + return sprintf($GLOBALS['LANG']->getLL('clearMakeExtDir_could_not_create_dir'), + $extDirPath); + } + return array($extDirPath); + } else return sprintf($GLOBALS['LANG']->getLL('clearMakeExtDir_no_dir'), + $path); + } + + /** + * Unlink (delete) cache files + * + * @return integer Number of deleted files. + */ + function removeCacheFiles() { + return t3lib_extMgm::removeCacheFiles(); + } + + /** + * Extracts the directories in the $files array + * + * @param array Array of files / directories + * @return array Array of directories from the input array. + */ + function extractDirsFromFileList($files) { + $dirs = array(); + + if (is_array($files)) { + // Traverse files / directories array: + foreach($files as $file) { + if (substr($file,-1)=='/') { + $dirs[$file] = $file; + } else { + $pI = pathinfo($file); + if (strcmp($pI['dirname'],'') && strcmp($pI['dirname'],'.')) { + $dirs[$pI['dirname'].'/'] = $pI['dirname'].'/'; + } + } + } + } + return $dirs; + } + + /** + * Returns the absolute path where the extension $extKey is installed (based on 'type' (SGL)) + * + * @param string Extension key + * @param string Install scope type: L, G, S + * @return string Returns the absolute path to the install scope given by input $type variable. It is checked if the path is a directory. Slash is appended. + */ + function getExtPath($extKey,$type) { + $typeP = $this->typePaths[$type]; + if ($typeP) { + $path = PATH_site.$typeP.$extKey.'/'; + return @is_dir($path) ? $path : ''; + } else { + return ''; + } + } + + + + + + + + + + + /******************************* + * + * Writing to "conf.php" and "localconf.php" files + * + ******************************/ + + /** + * Write new TYPO3_MOD_PATH to "conf.php" file. + * + * @param string Absolute path to a "conf.php" file of the backend module which we want to write back to. + * @param string Install scope type: L, G, S + * @param string Relative path for the module folder in extenson + * @return string Returns message about the status. + * @see modConfFileAnalysis() + */ + function writeTYPO3_MOD_PATH($confFilePath,$type,$mP) { + $lines = explode(LF,t3lib_div::getUrl($confFilePath)); + $confFileInfo = array(); + $confFileInfo['lines'] = $lines; + $reg = array(); + + $flag_M = 0; + $flag_B = 0; + $flag_Dispatch = 0; + + foreach($lines as $k => $l) { + $line = trim($l); + + unset($reg); + if (preg_match('/^define[[:space:]]*\([[:space:]]*["\']TYPO3_MOD_PATH["\'][[:space:]]*,[[:space:]]*["\']([[:alnum:]_\/\.]+)["\'][[:space:]]*\)[[:space:]]*;/',$line,$reg)) { + $lines[$k] = str_replace($reg[0], 'define(\'TYPO3_MOD_PATH\', \''.$this->typeRelPaths[$type].$mP.'\');', $lines[$k]); + $flag_M = $k+1; + } + + unset($reg); + if (preg_match('/^\$BACK_PATH[[:space:]]*=[[:space:]]*["\']([[:alnum:]_\/\.]+)["\'][[:space:]]*;/', $line, $reg)) { + $lines[$k] = str_replace($reg[0], '$BACK_PATH=\'' . $this->typeBackPaths[$type] . '\';', $lines[$k]); + $flag_B = $k + 1; + } + + // Check if this module uses new API (see http://bugs.typo3.org/view.php?id=5278) + // where TYPO3_MOD_PATH and BACK_PATH are not required + unset($reg); + if (preg_match('/^\$MCONF\[["\']script["\']\][[:space:]]*=[[:space:]]*["\']_DISPATCH["\'][[:space:]]*;/', $line, $reg)) { + $flag_Dispatch = $k+1; + } + + } + + if ($flag_B && $flag_M) { + t3lib_div::writeFile($confFilePath,implode(LF,$lines)); + return sprintf($GLOBALS['LANG']->getLL('writeModPath_ok'), + substr($confFilePath, strlen(PATH_site))); + } elseif ($flag_Dispatch){ + return sprintf( + $GLOBALS['LANG']->getLL('writeModPath_notRequired'), + substr($confFilePath, strlen(PATH_site)) + ); + } else return $GLOBALS["TBE_TEMPLATE"]->rfw( + sprintf($GLOBALS['LANG']->getLL('writeModPath_error'), + $confFilePath) + ); + } + + /** + * Writes the extension list to "localconf.php" file + * Removes the temp_CACHED* files before return. + * + * @param string List of extensions + * @return void + */ + function writeNewExtensionList($newExtList) { + global $TYPO3_CONF_VARS; + + $strippedExtensionList = $this->stripNonFrontendExtensions($newExtList); + + // Instance of install tool + $instObj = new t3lib_install; + $instObj->allowUpdateLocalConf =1; + $instObj->updateIdentity = 'TYPO3 Extension Manager'; + + // Get lines from localconf file + $lines = $instObj->writeToLocalconf_control(); + $instObj->setValueInLocalconfFile($lines, '$TYPO3_CONF_VARS[\'EXT\'][\'extList\']', $newExtList); + $instObj->setValueInLocalconfFile($lines, '$TYPO3_CONF_VARS[\'EXT\'][\'extList_FE\']', $strippedExtensionList); + $instObj->writeToLocalconf_control($lines); + + $TYPO3_CONF_VARS['EXT']['extList'] = $newExtList; + $TYPO3_CONF_VARS['EXT']['extList_FE'] = $strippedExtensionList; + $this->removeCacheFiles(); + } + + /** + * Removes unneeded extensions from the frontend based on + * EMCONF doNotLoadInFE = 1 + * + * @param string $extList + * @return string + */ + function stripNonFrontendExtensions($extList) { + $fullExtList = $this->getInstalledExtensions(); + $extListArray = t3lib_div::trimExplode(',', $extList); + foreach ($extListArray as $arrayKey => $extKey) { + if ($fullExtList[0][$extKey]['EM_CONF']['doNotLoadInFE'] == 1) { + unset($extListArray[$arrayKey]); + } + } + $nonFEList = implode(',', $extListArray); + return $nonFEList; + } + + /** + * Writes the TSstyleconf values to "localconf.php" + * Removes the temp_CACHED* files before return. + * + * @param string Extension key + * @param array Configuration array to write back + * @return void + */ + function writeTsStyleConfig($extKey,$arr) { + + // Instance of install tool + $instObj = new t3lib_install; + $instObj->allowUpdateLocalConf =1; + $instObj->updateIdentity = 'TYPO3 Extension Manager'; + + // Get lines from localconf file + $lines = $instObj->writeToLocalconf_control(); + $instObj->setValueInLocalconfFile($lines, '$TYPO3_CONF_VARS[\'EXT\'][\'extConf\'][\''.$extKey.'\']', serialize($arr)); // This will be saved only if there are no linebreaks in it ! + $instObj->writeToLocalconf_control($lines); + + $this->removeCacheFiles(); + } + + /** + * Forces update of local EM_CONF. This will renew the information of changed files. + * + * @param string Extension key + * @param array Extension information array + * @return string Status message + */ + function updateLocalEM_CONF($extKey,$extInfo) { + $extInfo['EM_CONF']['_md5_values_when_last_written'] = serialize($this->serverExtensionMD5Array($extKey,$extInfo)); + $emConfFileContent = $this->construct_ext_emconf_file($extKey,$extInfo['EM_CONF']); + + $absPath = $this->getExtPath($extKey,$extInfo['type']); + $emConfFileName = $absPath.'ext_emconf.php'; + if($emConfFileContent) { + + if(@is_file($emConfFileName)) { + if(t3lib_div::writeFile($emConfFileName,$emConfFileContent) === true) { + return sprintf($GLOBALS['LANG']->getLL('updateLocalEM_CONF_ok'), + substr($emConfFileName, strlen($absPath))); + } else { + return '' . sprintf($GLOBALS['LANG']->getLL('updateLocalEM_CONF_not_writable'), + $emConfFileName) . ''; + } + } else return('' . sprintf($GLOBALS['LANG']->getLL('updateLocalEM_CONF_not_found'), + $emConfFileName) . ''); + } else { + return sprintf($GLOBALS['LANG']->getLL('updateLocalEM_CONF_no_content'), + substr($emConfFileName, strlen($absPath))); + } + } + + + + + + + + + + + /******************************************* + * + * Compiling upload information, emconf-file etc. + * + *******************************************/ + + /** + * Compiles the ext_emconf.php file + * + * @param string Extension key + * @param array EM_CONF array + * @return string PHP file content, ready to write to ext_emconf.php file + */ + function construct_ext_emconf_file($extKey,$EM_CONF) { + + // clean version number: + $vDat = $this->renderVersion($EM_CONF['version']); + $EM_CONF['version']=$vDat['version']; + + $code = 'arrayToCode($EM_CONF, 0).'; + +?>'; + return str_replace(CR, '', $code); + } + + /** + * Enter description here... + * + * @param unknown_type $array + * @param unknown_type $lines + * @param unknown_type $level + * @return unknown + */ + function arrayToCode($array, $level=0) { + $lines = 'array('.LF; + $level++; + foreach($array as $k => $v) { + if(strlen($k) && is_array($v)) { + $lines .= str_repeat(TAB,$level)."'".$k."' => ".$this->arrayToCode($v, $level); + } elseif(strlen($k)) { + $lines .= str_repeat(TAB,$level)."'".$k."' => ".(t3lib_div::testInt($v) ? intval($v) : "'".t3lib_div::slashJS(trim($v),1)."'").','.LF; + } + } + + $lines .= str_repeat(TAB,$level-1).')'.($level-1==0 ? '':','.LF); + return $lines; + } + + /** + * Make upload array out of extension + * + * @param string Extension key + * @param array Extension information array + * @return mixed Returns array with extension upload array on success, otherwise an error string. + */ + function makeUploadArray($extKey,$conf) { + $extPath = $this->getExtPath($extKey,$conf['type']); + + if ($extPath) { + + // Get files for extension: + $fileArr = array(); + $fileArr = t3lib_div::getAllFilesAndFoldersInPath($fileArr,$extPath,'',0,99,$this->excludeForPackaging); + + // Calculate the total size of those files: + $totalSize = 0; + foreach($fileArr as $file) { + $totalSize+=filesize($file); + } + + // If the total size is less than the upper limit, proceed: + if ($totalSize < $this->maxUploadSize) { + + // Initialize output array: + $uploadArray = array(); + $uploadArray['extKey'] = $extKey; + $uploadArray['EM_CONF'] = $conf['EM_CONF']; + $uploadArray['misc']['codelines'] = 0; + $uploadArray['misc']['codebytes'] = 0; + + $uploadArray['techInfo'] = $this->makeDetailedExtensionAnalysis($extKey,$conf,1); + + // Read all files: + foreach($fileArr as $file) { + $relFileName = substr($file,strlen($extPath)); + $fI = pathinfo($relFileName); + if ($relFileName!='ext_emconf.php') { // This file should be dynamically written... + $uploadArray['FILES'][$relFileName] = array( + 'name' => $relFileName, + 'size' => filesize($file), + 'mtime' => filemtime($file), + 'is_executable' => (TYPO3_OS=='WIN' ? 0 : is_executable($file)), + 'content' => t3lib_div::getUrl($file) + ); + if (t3lib_div::inList('php,inc',strtolower($fI['extension']))) { + $uploadArray['FILES'][$relFileName]['codelines']=count(explode(LF,$uploadArray['FILES'][$relFileName]['content'])); + $uploadArray['misc']['codelines']+=$uploadArray['FILES'][$relFileName]['codelines']; + $uploadArray['misc']['codebytes']+=$uploadArray['FILES'][$relFileName]['size']; + + // locallang*.php files: + if (substr($fI['basename'],0,9)=='locallang' && strstr($uploadArray['FILES'][$relFileName]['content'],'$LOCAL_LANG')) { + $uploadArray['FILES'][$relFileName]['LOCAL_LANG']=$this->getSerializedLocalLang($file,$uploadArray['FILES'][$relFileName]['content']); + } + } + $uploadArray['FILES'][$relFileName]['content_md5'] = md5($uploadArray['FILES'][$relFileName]['content']); + } + } + + // Return upload-array: + return $uploadArray; + } else return sprintf($GLOBALS['LANG']->getLL('makeUploadArray_error_size'), + $totalSize, t3lib_div::formatSize($this->maxUploadSize)); + } else { + return sprintf($GLOBALS['LANG']->getLL('makeUploadArray_error_path'), + $extKey); + } + } + + /** + * Include a locallang file and return the $LOCAL_LANG array serialized. + * + * @param string Absolute path to locallang file to include. + * @param string Old content of a locallang file (keeping the header content) + * @return array Array with header/content as key 0/1 + * @see makeUploadArray() + */ + function getSerializedLocalLang($file, $content) { + $LOCAL_LANG = NULL; + $returnParts = explode('$LOCAL_LANG', $content, 2); + + include($file); + if (is_array($LOCAL_LANG)) { + $returnParts[1] = serialize($LOCAL_LANG); + return $returnParts; + } else { + return array(); + } + } + + + + + + + + + + + /******************************** + * + * Managing dependencies, conflicts, priorities, load order of extension keys + * + *******************************/ + + /** + * Adds extension to extension list and returns new list. If -1 is returned, an error happend. + * Checks dependencies etc. + * + * @param string Extension key + * @param array Extension information array - information about installed extensions + * @return string New list of installed extensions or -1 if error + * @see showExtDetails() + */ + function addExtToList($extKey,$instExtInfo) { + global $TYPO3_LOADED_EXT; + + // ext_emconf.php information: + $conf = $instExtInfo[$extKey]['EM_CONF']; + + // Get list of installed extensions and add this one. + $listArr = array_keys($TYPO3_LOADED_EXT); + if ($conf['priority']=='top') { + array_unshift($listArr,$extKey); + } else { + $listArr[]=$extKey; + } + + // Manage other circumstances: + $listArr = $this->managesPriorities($listArr,$instExtInfo); + $listArr = $this->removeRequiredExtFromListArr($listArr); + + // Implode unique list of extensions to load and return: + $list = implode(',',array_unique($listArr)); + return $list; + } + + /** + * Enter description here... + * + * @param string $extKey + * @param array $conf + * @param array $instExtInfo + * @return array + */ + function checkDependencies($extKey, $conf, $instExtInfo) { + $content = ''; + $depError = false; + $depIgnore = false; + $msg = array(); + $depsolver = t3lib_div::_POST('depsolver'); + + if (isset($conf['constraints']['depends']) && is_array($conf['constraints']['depends'])) { + foreach($conf['constraints']['depends'] as $depK => $depV) { + if($depsolver['ignore'][$depK]) { + $msg[] = '
' . sprintf($GLOBALS['LANG']->getLL('checkDependencies_ignored'), + $depK) . ' + '; + $depIgnore = true; + continue; + } + if($depK == 'php') { + if(!$depV) continue; + $versionRange = $this->splitVersionRange($depV); + $phpv = strstr(PHP_VERSION,'-') ? substr(PHP_VERSION,0,strpos(PHP_VERSION,'-')) : PHP_VERSION; // Linux distributors like to add suffixes, like in 5.1.2-1. Those must be ignored! + if ($versionRange[0]!='0.0.0' && version_compare($phpv,$versionRange[0],'<')) { + $msg[] = '
' . sprintf($GLOBALS['LANG']->getLL('checkDependencies_php_too_low'), + $phpv, $versionRange[0]); + $msg[] = '     + '; + $depError = true; + continue; + } elseif ($versionRange[1]!='0.0.0' && version_compare($phpv,$versionRange[1],'>')) { + $msg[] = '
' . sprintf($GLOBALS['LANG']->getLL('checkDependencies_php_too_high'), + $phpv, $versionRange[1]); + $msg[] = '     + '; + $depError = true; + continue; + } + + } elseif ($depK == 'typo3') { + if (!$depV) continue; + + // if the current TYPO3 version is a development version (like TYPO3 4.4-dev), + // then it should behave like TYPO3 4.4.0 + $t3version = TYPO3_version; + if (stripos($t3version, '-dev') + || stripos($t3version, '-alpha') + || stripos($t3version, '-beta') + || stripos($t3version, '-RC')) { + // find the last occurence of "-" and replace that part with a ".0" + $t3version = substr($t3version, 0, strrpos($t3version, '-')) . '.0'; + } + + $versionRange = $this->splitVersionRange($depV); + if ($versionRange[0]!='0.0.0' && version_compare($t3version, $versionRange[0], '<')) { + $msg[] = '
' . sprintf($GLOBALS['LANG']->getLL('checkDependencies_typo3_too_low'), + $t3version, $versionRange[0]); + $msg[] = '     + '; + $depError = true; + continue; + } elseif ($versionRange[1]!='0.0.0' && version_compare($t3version, $versionRange[1], '>')) { + $msg[] = '
' . sprintf($GLOBALS['LANG']->getLL('checkDependencies_typo3_too_high'), + $t3version, $versionRange[1]); + $msg[] = '     + '; + $depError = true; + continue; + } + } elseif (strlen($depK) && !t3lib_extMgm::isLoaded($depK)) { // strlen check for braindead empty dependencies coming from extensions... + if(!isset($instExtInfo[$depK])) { + $msg[] = '
' . sprintf($GLOBALS['LANG']->getLL('checkDependencies_ext_not_available'), + $depK); + $msg[] = '    ' . t3lib_iconWorks::getSpriteIcon('actions-system-extension-import', array('title' => $GLOBALS['LANG']->getLL('checkDependencies_import_ext'))) . '  + ' . $GLOBALS['LANG']->getLL('checkDependencies_import_now') . ''; + $msg[] = '     + '; + } else { + $msg[] = '
' . sprintf($GLOBALS['LANG']->getLL('checkDependencies_ext_not_installed'), + $depK, $instExtInfo[$depK]['EM_CONF']['title']); + $msg[] = '    ' . $this->installButton() . '  + ' . $GLOBALS['LANG']->getLL('checkDependencies_install_now') . ''; + $msg[] = '     + '; + } + $depError = true; + } else { + $versionRange = $this->splitVersionRange($depV); + if ($versionRange[0]!='0.0.0' && version_compare($instExtInfo[$depK]['EM_CONF']['version'],$versionRange[0],'<')) { + $msg[] = '
' . sprintf($GLOBALS['LANG']->getLL('checkDependencies_ext_too_low'), + $depK, $instExtInfo[$depK]['EM_CONF']['version'], $versionRange[0]); + $msg[] = '     + '; + $depError = true; + continue; + } elseif ($versionRange[1]!='0.0.0' && version_compare($instExtInfo[$depK]['EM_CONF']['version'],$versionRange[1],'>')) { + $msg[] = '
' . sprintf($GLOBALS['LANG']->getLL('checkDependencies_ext_too_high'), + $depK, $instExtInfo[$depK]['EM_CONF']['version'], $versionRange[1]); + $msg[] = '     + '; + $depError = true; + continue; + } + } + } + } + if($depError || $depIgnore) { + $content .= $this->doc->section( + $GLOBALS['LANG']->getLL('removeExtFromList_dependency_error'), + implode('
', $msg), 0, 1, 2 + ); + } + + // Check conflicts with other extensions: + $conflictError = false; + $conflictIgnore = false; + $msg = array(); + + if (isset($conf['constraints']['conflicts']) && is_array($conf['constraints']['conflicts'])) { + foreach((array)$conf['constraints']['conflicts'] as $conflictK => $conflictV) { + if($depsolver['ignore'][$conflictK]) { + $msg[] = '
' . sprintf($GLOBALS['LANG']->getLL('checkDependencies_conflict_ignored'), + $conflictK) . ' + '; + $conflictIgnore = true; + continue; + } + if (t3lib_extMgm::isLoaded($conflictK)) { + $versionRange = $this->splitVersionRange($conflictV); + if ($versionRange[0] != '0.0.0' && version_compare($instExtInfo[$conflictK]['EM_CONF']['version'],$versionRange[0],'<')) { + continue; + } + elseif ($versionRange[1] != '0.0.0' && version_compare($instExtInfo[$conflictK]['EM_CONF']['version'],$versionRange[1],'>')) { + continue; + } + $msg[] = sprintf($GLOBALS['LANG']->getLL('checkDependencies_conflict_remove'), + $extKey, $conflictK, $instExtInfo[$conflictK]['EM_CONF']['title'], $conflictK, $extKey); + $msg[] = '    ' . $this->removeButton() . '  + ' . $GLOBALS['LANG']->getLL('checkDependencies_remove_now') . ''; + $msg[] = '     + '; + $conflictError = true; + } + } + } + if($conflictError || $conflictIgnore) { + $content .= $this->doc->section( + $GLOBALS['LANG']->getLL('checkDependencies_conflict_error'), implode('
', $msg), 0, 1, 2 + ); + } + + // Check suggests on other extensions: + if(isset($conf['constraints']['suggests']) && is_array($conf['constraints']['suggests'])) { + $suggestion = false; + $suggestionIgnore = false; + $msg = array(); + foreach($conf['constraints']['suggests'] as $suggestK => $suggestV) { + if($depsolver['ignore'][$suggestK]) { + $msg[] = '
' . sprintf($GLOBALS['LANG']->getLL('checkDependencies_suggestion_ignored'), + $suggestK) . ' + '; + $suggestionIgnore = true; + continue; + } + if (!t3lib_extMgm::isLoaded($suggestK)) { + if (!isset($instExtInfo[$suggestK])) { + $msg[] = sprintf($GLOBALS['LANG']->getLL('checkDependencies_suggest_import'), + $suggestK); + $msg[] = '    ' . t3lib_iconWorks::getSpriteIcon('actions-system-extension-import', array('title' => $GLOBALS['LANG']->getLL('checkDependencies_import_ext'))) . '  + ' . $GLOBALS['LANG']->getLL('checkDependencies_import_now') . ''; + $msg[] = '     + '; + } else { + $msg[] = sprintf($GLOBALS['LANG']->getLL('checkDependencies_suggest_installation'), + $suggestK, $instExtInfo[$suggestK]['EM_CONF']['title']); + $msg[] = '    ' . $this->installButton() . '  + ' . $GLOBALS['LANG']->getLL('checkDependencies_install_now') . ''; + $msg[] = '     + '; + } + $suggestion = true; + } + } + if($suggestion || $suggestionIgnore) { + $content .= $this->doc->section( + sprintf($GLOBALS['LANG']->getLL('checkDependencies_exts_suggested_by_ext'), $extKey), + implode('
', $msg), 0, 1, 1 + ); + } + } + + if($depError || $conflictError || $suggestion) { + foreach($this->CMD as $k => $v) { + $content .= ''; + } + $content .= '

'; + + return array('returnCode' => false, 'html' => '
'.$content.'
'); + } + + return array('returnCode' => true); + } + + /** + * Remove extension key from the list of currently installed extensions and return list. If -1 is returned, an error happend. + * Checks dependencies etc. + * + * @param string Extension key + * @param array Extension information array - information about installed extensions + * @return string New list of installed extensions or -1 if error + * @see showExtDetails() + */ + function removeExtFromList($extKey,$instExtInfo) { + global $TYPO3_LOADED_EXT; + + // Initialize: + $depList = array(); + $listArr = array_keys($TYPO3_LOADED_EXT); + + // Traverse all installed extensions to check if any of them have this extension as dependency since if that is the case it will not work out! + foreach($listArr as $k => $ext) { + if ($instExtInfo[$ext]['EM_CONF']['dependencies']) { + $dep = t3lib_div::trimExplode(',',$instExtInfo[$ext]['EM_CONF']['dependencies'],1); + if (in_array($extKey,$dep)) { + $depList[] = $ext; + } + } + if (!strcmp($ext,$extKey)) unset($listArr[$k]); + } + + // Returns either error or the new list + if (count($depList)) { + $msg = sprintf($GLOBALS['LANG']->getLL('removeExtFromList_dependency'), + implode(', ', $depList) + ); + $this->content .= $this->doc->section($GLOBALS['LANG']->getLL('removeExtFromList_dependency_error'), $msg, 0, 1, 2); + return -1; + } else { + $listArr = $this->removeRequiredExtFromListArr($listArr); + $list = implode(',',array_unique($listArr)); + return $list; + } + } + + /** + * This removes any required extensions from the $listArr - they should NOT be added to the common extension list, because they are found already in "requiredExt" list + * + * @param array Array of extension keys as values + * @return array Modified array + * @see removeExtFromList(), addExtToList() + */ + function removeRequiredExtFromListArr($listArr) { + foreach($listArr as $k => $ext) { + if (in_array($ext,$this->requiredExt) || !strcmp($ext,'_CACHEFILE')) unset($listArr[$k]); + } + return $listArr; + } + + /** + * Traverse the array of installed extensions keys and arranges extensions in the priority order they should be in + * + * @param array Array of extension keys as values + * @param array Extension information array + * @return array Modified array of extention keys as values + * @see addExtToList() + */ + function managesPriorities($listArr,$instExtInfo) { + + // Initialize: + $levels = array( + 'top' => array(), + 'middle' => array(), + 'bottom' => array(), + ); + + // Traverse list of extensions: + foreach($listArr as $ext) { + $prio = trim($instExtInfo[$ext]['EM_CONF']['priority']); + switch((string)$prio) { + case 'top': + case 'bottom': + $levels[$prio][] = $ext; + break; + default: + $levels['middle'][] = $ext; + break; + } + } + return array_merge( + $levels['top'], + $levels['middle'], + $levels['bottom'] + ); + } + + + + + + + + + + + /******************************* + * + * System Update functions (based on extension requirements) + * + ******************************/ + + /** + * Check if clear-cache should be performed, otherwise show form (for installation of extension) + * Shown only if the extension has the clearCacheOnLoad flag set. + * + * @param string Extension key + * @param array Extension information array + * @return string HTML output (if form is shown) + */ + function checkClearCache($extInfo) { + if ($extInfo['EM_CONF']['clearCacheOnLoad']) { + if (t3lib_div::_POST('_clear_all_cache')) { // Action: Clearing the cache + $tce = t3lib_div::makeInstance('t3lib_TCEmain'); + $tce->stripslashes_values = 0; + $tce->start(Array(),Array()); + $tce->clear_cacheCmd('all'); + } else { // Show checkbox for clearing cache: + $content.= ' +
+

' . $GLOBALS['LANG']->getLL('checkUploadFolder_clear_cache') . '

+

' . $GLOBALS['LANG']->getLL('checkUploadFolder_clear_cache_requested') . '
+ +
+

+ '; + } + } + return $content; + } + + /** + * Check if upload folder / "createDir" directories should be created. + * + * @param string Extension key + * @param array Extension information array + * @return string HTML content. + */ + function checkUploadFolder($extKey,$extInfo) { + + // Checking for upload folder: + $uploadFolder = PATH_site.$this->ulFolder($extKey); + if ($extInfo['EM_CONF']['uploadfolder'] && !@is_dir($uploadFolder)) { + if (t3lib_div::_POST('_uploadfolder')) { // CREATE dir: + t3lib_div::mkdir($uploadFolder); + $indexContent = ' + + + + + +'; + t3lib_div::writeFile($uploadFolder.'index.html',$indexContent); + } else { // Show checkbox / HTML for creation: + $content.=' +

' . $GLOBALS['LANG']->getLL('checkUploadFolder_create_upload_folder') . '

+

' . sprintf($GLOBALS['LANG']->getLL('checkUploadFolder_upload_folder_needed'), + $this->ulFolder($extKey) + ) . '
+ +
+

+ '; + } + } + + // Additional directories that should be created: + if ($extInfo['EM_CONF']['createDirs']) { + $createDirs = array_unique(t3lib_div::trimExplode(',',$extInfo['EM_CONF']['createDirs'],1)); + + foreach($createDirs as $crDir) { + if (!@is_dir(PATH_site.$crDir)) { + if (t3lib_div::_POST('_createDir_'.md5($crDir))) { // CREATE dir: + + // Initialize: + $crDirStart = ''; + $dirs_in_path = explode('/',preg_replace('/\/$/','',$crDir)); + + // Traverse each part of the dir path and create it one-by-one: + foreach($dirs_in_path as $dirP) { + if (strcmp($dirP,'')) { + $crDirStart.= $dirP.'/'; + if (!@is_dir(PATH_site.$crDirStart)) { + t3lib_div::mkdir(PATH_site.$crDirStart); + $finalDir = PATH_site.$crDirStart; + } + } else { + throw new RuntimeException( + 'TYPO3 Fatal Error: ' . sprintf($GLOBALS['LANG']->getLL('checkUploadFolder_error'), PATH_site . $crDir), + 1270853982 + ); + } + } + if ($finalDir) { + $indexContent = ' + + + + + +'; + t3lib_div::writeFile($finalDir.'index.html',$indexContent); + } + } else { // Show checkbox / HTML for creation: + $md5CrDir = md5($crDir); + $content.=' +
+

' . $GLOBALS['LANG']->getLL('checkUploadFolder_create_folder') . '

+

' . sprintf($GLOBALS['LANG']->getLL('checkUploadFolder_folder_needed'), + $crDir + ) . '
+ +
+

+ '; + } + } + } + } + + return $content; + } + + /** + * Validates the database according to extension requirements + * Prints form for changes if any. If none, returns blank. If an update is ordered, empty is returned as well. + * DBAL compliant (based on Install Tool code) + * + * @param string Extension key + * @param array Extension information array + * @param boolean If true, returns array with info. + * @return mixed If $infoOnly, returns array with information. Otherwise performs update. + */ + function checkDBupdates($extKey,$extInfo,$infoOnly=0) { + + // Initializing Install Tool object: + $instObj = new t3lib_install; + $instObj->INSTALL = t3lib_div::_GP('TYPO3_INSTALL'); + $dbStatus = array(); + + // Updating tables and fields? + if (is_array($extInfo['files']) && in_array('ext_tables.sql', $extInfo['files'])) { + $fileContent = t3lib_div::getUrl($this->getExtPath($extKey,$extInfo['type']).'ext_tables.sql'); + + $FDfile = $instObj->getFieldDefinitions_fileContent($fileContent); + if (count($FDfile)) { + $FDdb = $instObj->getFieldDefinitions_database(TYPO3_db); + $diff = $instObj->getDatabaseExtra($FDfile, $FDdb); + $update_statements = $instObj->getUpdateSuggestions($diff); + + $dbStatus['structure']['tables_fields'] = $FDfile; + $dbStatus['structure']['diff'] = $diff; + + // Updating database... + if (!$infoOnly && is_array($instObj->INSTALL['database_update'])) { + $instObj->performUpdateQueries($update_statements['add'],$instObj->INSTALL['database_update']); + $instObj->performUpdateQueries($update_statements['change'],$instObj->INSTALL['database_update']); + $instObj->performUpdateQueries($update_statements['create_table'],$instObj->INSTALL['database_update']); + } else { + $content .= $instObj->generateUpdateDatabaseForm_checkboxes( + $update_statements['add'], $GLOBALS['LANG']->getLL('checkDBupdates_add_fields')); + $content .= $instObj->generateUpdateDatabaseForm_checkboxes( + $update_statements['change'], $GLOBALS['LANG']->getLL('checkDBupdates_changing_fields'), 1, 0, $update_statements['change_currentValue']); + $content .= $instObj->generateUpdateDatabaseForm_checkboxes( + $update_statements['create_table'], $GLOBALS['LANG']->getLL('checkDBupdates_add_tables')); + } + } + } + + // Importing static tables? + if (is_array($extInfo['files']) && in_array('ext_tables_static+adt.sql',$extInfo['files'])) { + $fileContent = t3lib_div::getUrl($this->getExtPath($extKey,$extInfo['type']).'ext_tables_static+adt.sql'); + + $statements = $instObj->getStatementArray($fileContent,1); + list($statements_table, $insertCount) = $instObj->getCreateTables($statements,1); + + // Execute import of static table content: + if (!$infoOnly && is_array($instObj->INSTALL['database_import'])) { + + // Traverse the tables + foreach($instObj->INSTALL['database_import'] as $table => $md5str) { + if ($md5str == md5($statements_table[$table])) { + $GLOBALS['TYPO3_DB']->admin_query('DROP TABLE IF EXISTS '.$table); + $GLOBALS['TYPO3_DB']->admin_query($statements_table[$table]); + + if ($insertCount[$table]) { + $statements_insert = $instObj->getTableInsertStatements($statements, $table); + + foreach($statements_insert as $v) { + $GLOBALS['TYPO3_DB']->admin_query($v); + } + } + } + } + } else { + $whichTables = $instObj->getListOfTables(); + if (count($statements_table)) { + $out = ''; + foreach($statements_table as $table => $definition) { + $exist = isset($whichTables[$table]); + + $dbStatus['static'][$table]['exists'] = $exist; + $dbStatus['static'][$table]['count'] = $insertCount[$table]; + + $out.= ' + + '.$table.' + + ' . + ($insertCount[$table] ? + $GLOBALS['LANG']->getLL('checkDBupdates_rows') . ' ' . $insertCount[$table] + : '') . + ' + + ' . + ($exist ? + t3lib_iconWorks::getSpriteIcon('status-dialog-warning') . + $GLOBALS['LANG']->getLL('checkDBupdates_table_exists') + : '') . + ' + '; + } + $content.= ' +
+

' . $GLOBALS['LANG']->getLL('checkDBupdates_import_static_data') . '

+ '.$out.'
+ '; + } + } + } + + // Return array of information if $infoOnly, otherwise content. + return $infoOnly ? $dbStatus : $content; + } + + /** + * Updates the database according to extension requirements + * DBAL compliant (based on Install Tool code) + * + * @param string Extension key + * @param array Extension information array + * @return void + */ + function forceDBupdates($extKey, $extInfo) { + $instObj = new t3lib_install; + + // Updating tables and fields? + if (is_array($extInfo['files']) && in_array('ext_tables.sql',$extInfo['files'])) { + $fileContent = t3lib_div::getUrl($this->getExtPath($extKey,$extInfo['type']).'ext_tables.sql'); + + $FDfile = $instObj->getFieldDefinitions_fileContent($fileContent); + if (count($FDfile)) { + $FDdb = $instObj->getFieldDefinitions_database(TYPO3_db); + $diff = $instObj->getDatabaseExtra($FDfile, $FDdb); + $update_statements = $instObj->getUpdateSuggestions($diff); + + foreach((array)$update_statements['add'] as $string) { + $GLOBALS['TYPO3_DB']->admin_query($string); + } + foreach((array)$update_statements['change'] as $string) { + $GLOBALS['TYPO3_DB']->admin_query($string); + } + foreach((array)$update_statements['create_table'] as $string) { + $GLOBALS['TYPO3_DB']->admin_query($string); + } + } + } + + // Importing static tables? + if (is_array($extInfo['files']) && in_array('ext_tables_static+adt.sql',$extInfo['files'])) { + $fileContent = t3lib_div::getUrl($this->getExtPath($extKey,$extInfo['type']).'ext_tables_static+adt.sql'); + + $statements = $instObj->getStatementArray($fileContent,1); + list($statements_table, $insertCount) = $instObj->getCreateTables($statements,1); + + // Traverse the tables + foreach($statements_table as $table => $query) { + $GLOBALS['TYPO3_DB']->admin_query('DROP TABLE IF EXISTS '.$table); + $GLOBALS['TYPO3_DB']->admin_query($query); + + if ($insertCount[$table]) { + $statements_insert = $instObj->getTableInsertStatements($statements, $table); + + foreach($statements_insert as $v) { + $GLOBALS['TYPO3_DB']->admin_query($v); + } + } + } + } + } + + /** + * Produces the config form for an extension (if any template file, ext_conf_template.txt is found) + * + * @param string Extension key + * @param array Extension information array + * @param boolean If true, the form HTML content is returned, otherwise the content is set in $this->content. + * @param string Submit-to URL (supposedly) + * @param string Additional form fields to include. + * @return string Depending on $output. Can return the whole form. + */ + function tsStyleConfigForm($extKey,$extInfo,$output=0,$script='',$addFields='') { + global $TYPO3_CONF_VARS; + + // Initialize: + $absPath = $this->getExtPath($extKey,$extInfo['type']); + $relPath = $this->typeRelPaths[$extInfo['type']].$extKey.'/'; + + // Look for template file for form: + if (t3lib_extMgm::isLoaded($extKey) && @is_file($absPath.'ext_conf_template.txt')) { + + // Load tsStyleConfig class and parse configuration template: + $tsStyleConfig = t3lib_div::makeInstance('t3lib_tsStyleConfig'); + $tsStyleConfig->doNotSortCategoriesBeforeMakingForm = TRUE; + $theConstants = $tsStyleConfig->ext_initTSstyleConfig( + t3lib_div::getUrl($absPath.'ext_conf_template.txt'), + $relPath, + $absPath, + $GLOBALS['BACK_PATH'] + ); + + // Load the list of resources. + $tsStyleConfig->ext_loadResources($absPath.'res/'); + + // Load current value: + $arr = unserialize($TYPO3_CONF_VARS['EXT']['extConf'][$extKey]); + $arr = is_array($arr) ? $arr : array(); + + // Call processing function for constants config and data before write and form rendering: + if (is_array($TYPO3_CONF_VARS['SC_OPTIONS']['typo3/mod/tools/em/index.php']['tsStyleConfigForm'])) { + $_params = array('fields' => &$theConstants, 'data' => &$arr, 'extKey' => $extKey); + foreach($TYPO3_CONF_VARS['SC_OPTIONS']['typo3/mod/tools/em/index.php']['tsStyleConfigForm'] as $_funcRef) { + t3lib_div::callUserFunction($_funcRef,$_params,$this); + } + unset($_params); + } + + // If saving operation is done: + if (t3lib_div::_POST('submit')) { + $tsStyleConfig->ext_procesInput(t3lib_div::_POST(),array(),$theConstants,array()); + $arr = $tsStyleConfig->ext_mergeIncomingWithExisting($arr); + $this->writeTsStyleConfig($extKey,$arr); + } + + // Setting value array + $tsStyleConfig->ext_setValueArray($theConstants,$arr); + + // Getting session data: + $MOD_MENU = array(); + $MOD_MENU['constant_editor_cat'] = $tsStyleConfig->ext_getCategoriesForModMenu(); + $MOD_SETTINGS = t3lib_BEfunc::getModuleData($MOD_MENU, t3lib_div::_GP('SET'), 'xMod_test'); + + // Resetting the menu (stop) + if (count($MOD_MENU['constant_editor_cat']) > 1) { + $menu = $GLOBALS['LANG']->getLL('extInfoArray_category') . ' ' . + t3lib_BEfunc::getFuncMenu(0, 'SET[constant_editor_cat]', $MOD_SETTINGS['constant_editor_cat'], $MOD_MENU['constant_editor_cat'], '', '&CMD[showExt]=' . $extKey); + $this->content.=$this->doc->section('',''.$menu.''); + $this->content.=$this->doc->spacer(10); + } + + // Category and constant editor config: + $form = ' + + + + +
'.$tsStyleConfig->ext_getForm($MOD_SETTINGS['constant_editor_cat'],$theConstants,$script,$addFields).'
'; + } else { + $flashMessage = t3lib_div::makeInstance( + 't3lib_FlashMessage', + $GLOBALS['LANG']->getLL('tsStyleConfigForm_additional_config'), + '', + t3lib_FlashMessage::INFO + ); + + $form = ' + + + + +
+
' . + $addFields . + $flashMessage->render() . + '
+
+
'; + } + + if ($output) { + return $form; + } else { + $this->content.=$this->doc->section('', $form); + } + + } + + + + + + + + + + + /******************************* + * + * Dumping database (MySQL compliant) + * + ******************************/ + + /** + * Makes a dump of the tables/fields definitions for an extension + * + * @param array Array with table => field/key definition arrays in + * @return string SQL for the table definitions + * @see dumpStaticTables() + */ + function dumpTableAndFieldStructure($arr) { + $tables = array(); + + if (count($arr)) { + + // Get file header comment: + $tables[] = $this->dumpHeader(); + + // Traverse tables, write each table/field definition: + foreach($arr as $table => $fieldKeyInfo) { + $tables[] = $this->dumpTableHeader($table,$fieldKeyInfo); + } + } + + // Return result: + return implode(LF.LF.LF,$tables); + } + + /** + * Dump content for static tables + * + * @param string Comma list of tables from which to dump content + * @return string Returns the content + * @see dumpTableAndFieldStructure() + */ + function dumpStaticTables($tableList) { + $instObj = new t3lib_install; + $dbFields = $instObj->getFieldDefinitions_database(TYPO3_db); + + $out = ''; + $parts = t3lib_div::trimExplode(',',$tableList,1); + + // Traverse the table list and dump each: + foreach($parts as $table) { + if (is_array($dbFields[$table]['fields'])) { + $dHeader = $this->dumpHeader(); + $header = $this->dumpTableHeader($table,$dbFields[$table],1); + $insertStatements = $this->dumpTableContent($table,$dbFields[$table]['fields']); + + $out.= $dHeader.LF.LF.LF. + $header.LF.LF.LF. + $insertStatements.LF.LF.LF; + } else { + throw new RuntimeException( + 'TYPO3 Fatal Error: ' . $GLOBALS['LANG']->getLL('dumpStaticTables_table_not_found'), + 1270853983 + ); + } + } + return $out; + } + + /** + * Header comments of the SQL dump file + * + * @return string Table header + */ + function dumpHeader() { + return trim(' +# TYPO3 Extension Manager dump 1.1 +# +# Host: '.TYPO3_db_host.' Database: '.TYPO3_db.' +#-------------------------------------------------------- +'); + } + + /** + * Dump CREATE TABLE definition + * + * @param string Table name + * @param array Field and key information (as provided from Install Tool class!) + * @param boolean If true, add "DROP TABLE IF EXISTS" + * @return string Table definition SQL + */ + function dumpTableHeader($table,$fieldKeyInfo,$dropTableIfExists=0) { + $lines = array(); + $dump = ''; + + // Create field definitions + if (is_array($fieldKeyInfo['fields'])) { + foreach($fieldKeyInfo['fields'] as $fieldN => $data) { + $lines[]=' '.$fieldN.' '.$data; + } + } + + // Create index key definitions + if (is_array($fieldKeyInfo['keys'])) { + foreach($fieldKeyInfo['keys'] as $fieldN => $data) { + $lines[]=' '.$data; + } + } + + // Compile final output: + if (count($lines)) { + $dump = trim(' +# +# Table structure for table "'.$table.'" +# +'.($dropTableIfExists ? 'DROP TABLE IF EXISTS '.$table.'; +' : '').'CREATE TABLE '.$table.' ( +'.implode(','.LF,$lines).' +);' +); + } + + return $dump; + } + + /** + * Dump table content + * Is DBAL compliant, but the dump format is written as MySQL standard. If the INSERT statements should be imported in a DBMS using other quoting than MySQL they must first be translated. t3lib_sqlengine can parse these queries correctly and translate them somehow. + * + * @param string Table name + * @param array Field structure + * @return string SQL Content of dump (INSERT statements) + */ + function dumpTableContent($table,$fieldStructure) { + + // Substitution of certain characters (borrowed from phpMySQL): + $search = array('\\', '\'', "\x00", "\x0a", "\x0d", "\x1a"); + $replace = array('\\\\', '\\\'', '\0', '\n', '\r', '\Z'); + + $lines = array(); + + // Select all rows from the table: + $result = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', $table, ''); + + // Traverse the selected rows and dump each row as a line in the file: + while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($result)) { + $values = array(); + foreach ($fieldStructure as $field => $dummyValue) { + $values[] = isset($row[$field]) ? "'".str_replace($search, $replace, $row[$field])."'" : 'NULL'; + } + $lines[] = 'INSERT INTO '.$table.' VALUES ('.implode(', ',$values).');'; + } + + // Free DB result: + $GLOBALS['TYPO3_DB']->sql_free_result($result); + + // Implode lines and return: + return implode(LF,$lines); + } + + /** + * Gets the table and field structure from database. + * Which fields and which tables are determined from the ext_tables.sql file + * + * @param string Array with table.field values + * @return array Array of tables and fields splitted. + */ + function getTableAndFieldStructure($parts) { + // Instance of install tool + $instObj = new t3lib_install; + $dbFields = $instObj->getFieldDefinitions_database(TYPO3_db); + + + $outTables = array(); + foreach($parts as $table) { + $tP = explode('.',$table); + if ($tP[0] && isset($dbFields[$tP[0]])) { + if ($tP[1]) { + $kfP = explode('KEY:',$tP[1],2); + if (count($kfP)==2 && !$kfP[0]) { // key: + if (isset($dbFields[$tP[0]]['keys'][$kfP[1]])) $outTables[$tP[0]]['keys'][$kfP[1]] = $dbFields[$tP[0]]['keys'][$kfP[1]]; + } else { + if (isset($dbFields[$tP[0]]['fields'][$tP[1]])) $outTables[$tP[0]]['fields'][$tP[1]] = $dbFields[$tP[0]]['fields'][$tP[1]]; + } + } else { + $outTables[$tP[0]] = $dbFields[$tP[0]]; + } + } + } + + return $outTables; + } + + + + + + + + + + + /******************************* + * + * TER Communication functions + * + ******************************/ + + + + /** + * Processes return-data from online repository. + * Currently only the returned emconf array is written to extension. + * + * @param array Command array returned from TER + * @return string Message + */ + function uploadExtensionToTER($em) { + $msg = ''; + $response = $this->terConnection->uploadToTER($em); + + if(!is_array($response)) return $response; + + if($response['resultCode']==TX_TER_RESULT_EXTENSIONSUCCESSFULLYUPLOADED) { + $em['extInfo']['EM_CONF']['version'] = $response['version']; + $response['resultMessages'][] = sprintf($GLOBALS['LANG']->getLL('terCommunication_ext_version'), + $response['version'] + ); + $response['resultMessages'][] = $this->updateLocalEM_CONF($em['extKey'],$em['extInfo']); + } + + $msg = '
  • '.implode('
  • ',$response['resultMessages']).'
'; + return $msg; + } + + + + + + + + + + + /************************************ + * + * Various helper functions + * + ************************************/ + + /** + * Returns subtitles for the extension listings + * + * @param string List order type + * @param string Key value + * @return string output. + */ + function listOrderTitle($listOrder,$key) { + switch($listOrder) { + case 'cat': + return isset($this->categories[$key]) ? $this->categories[$key] : '[' . $key . ']'; + break; + case 'author_company': + return $key; + break; + case 'state': + return $this->states[$key]; + break; + case 'type': + return $this->typeDescr[$key]; + break; + } + } + + /** + * Returns version information + * + * @param string Version code, x.x.x + * @param string part: "", "int", "main", "sub", "dev" + * @return string + * @see renderVersion() + */ + function makeVersion($v,$mode) { + $vDat = $this->renderVersion($v); + return $vDat['version_'.$mode]; + } + + /** + * Parses the version number x.x.x and returns an array with the various parts. + * + * @param string Version code, x.x.x + * @param string Increase version part: "main", "sub", "dev" + * @return string + */ + function renderVersion($v,$raise='') { + $parts = t3lib_div::intExplode('.',$v.'..'); + $parts[0] = t3lib_div::intInRange($parts[0],0,999); + $parts[1] = t3lib_div::intInRange($parts[1],0,999); + $parts[2] = t3lib_div::intInRange($parts[2],0,999); + + switch((string)$raise) { + case 'main': + $parts[0]++; + $parts[1]=0; + $parts[2]=0; + break; + case 'sub': + $parts[1]++; + $parts[2]=0; + break; + case 'dev': + $parts[2]++; + break; + } + + $res = array(); + $res['version'] = $parts[0].'.'.$parts[1].'.'.$parts[2]; + $res['version_int'] = intval($parts[0]*1000000+$parts[1]*1000+$parts[2]); + $res['version_main'] = $parts[0]; + $res['version_sub'] = $parts[1]; + $res['version_dev'] = $parts[2]; + + return $res; + } + + /** + * Returns upload folder for extension + * + * @param string Extension key + * @return string Upload folder for extension + */ + function ulFolder($extKey) { + return 'uploads/tx_'.str_replace('_','',$extKey).'/'; + } + + /** + * Returns true if global OR local installation of extensions is allowed/possible. + * + * @return boolean Returns true if global OR local installation of extensions is allowed/possible. + */ + function importAtAll() { + return ($GLOBALS['TYPO3_CONF_VARS']['EXT']['allowGlobalInstall'] || $GLOBALS['TYPO3_CONF_VARS']['EXT']['allowLocalInstall']); + } + + /** + * Reports back if installation in a certain scope is possible. + * + * @param string Scope: G, L, S + * @param string Extension lock-type (eg. "L" or "G") + * @return boolean True if installation is allowed. + */ + function importAsType($type,$lockType='') { + switch($type) { + case 'G': + return $GLOBALS['TYPO3_CONF_VARS']['EXT']['allowGlobalInstall'] && (!$lockType || !strcmp($lockType,$type)); + break; + case 'L': + return $GLOBALS['TYPO3_CONF_VARS']['EXT']['allowLocalInstall'] && (!$lockType || !strcmp($lockType,$type)); + break; + case 'S': + return $this->systemInstall; + break; + default: + return false; + } + } + + /** + * Returns true if extensions in scope, $type, can be deleted (or installed for that sake) + * + * @param string Scope: "G" or "L" + * @return boolean True if possible. + */ + function deleteAsType($type) { + switch($type) { + case 'G': + return $GLOBALS['TYPO3_CONF_VARS']['EXT']['allowGlobalInstall']; + break; + case 'L': + return $GLOBALS['TYPO3_CONF_VARS']['EXT']['allowLocalInstall']; + break; + default: + return false; + } + } + + /** + * Evaluates differences in version numbers with three parts, x.x.x. Returns true if $v1 is greater than $v2 + * + * @param string Version number 1 + * @param string Version number 2 + * @param integer Tolerance factor. For instance, set to 1000 to ignore difference in dev-version (third part) + * @return boolean True if version 1 is greater than version 2 + */ + function versionDifference($v1,$v2,$div=1) { + return floor($this->makeVersion($v1,'int')/$div) > floor($this->makeVersion($v2,'int')/$div); + } + + /** + * Returns true if the $str is found as the first part of a string in $array + * + * @param string String to test with. + * @param array Input array + * @param boolean If set, the test is case insensitive + * @return boolean True if found. + */ + function first_in_array($str,$array,$caseInsensitive=FALSE) { + if ($caseInsensitive) $str = strtolower($str); + if (is_array($array)) { + foreach($array as $cl) { + if ($caseInsensitive) $cl = strtolower($cl); + if (t3lib_div::isFirstPartOfStr($cl,$str)) return true; + } + } + return false; + } + + /** + * Returns the $EM_CONF array from an extensions ext_emconf.php file + * + * @param string Absolute path to EMCONF file. + * @param string Extension key. + * @return array EMconf array values. + */ + function includeEMCONF($path, $_EXTKEY) { + $EM_CONF = NULL; + @include($path); + if(is_array($EM_CONF[$_EXTKEY])) { + return $this->fixEMCONF($EM_CONF[$_EXTKEY]); + } + return false; + } + + /** + * Searches for ->lookUpStr in extension and returns true if found (or if no search string is set) + * + * @param string Extension key + * @param array Extension content + * @return boolean If true, display extension in list + */ + function searchExtension($extKey,$row) { + if ($this->lookUpStr) { + return ( + stristr($extKey,$this->lookUpStr) || + stristr($row['EM_CONF']['title'],$this->lookUpStr) || + stristr($row['EM_CONF']['description'],$this->lookUpStr) || + stristr($row['EM_CONF']['author'],$this->lookUpStr) || + stristr($row['EM_CONF']['author_company'],$this->lookUpStr) + ); + } else return true; + } + + + + /** + * Checks if there are newer versions of installed extensions in the TER + * integrated from the extension "ter_update_check" for TYPO3 4.2 by Christian Welzel + * + * @return nothing + */ + function checkForUpdates() { + global $LANG; + $content = ''; + + if (is_file(PATH_site.'typo3temp/extensions.xml.gz')) { + $content = $this->showExtensionsToUpdate() + .t3lib_BEfunc::getFuncCheck(0, 'SET[display_installed]', $this->MOD_SETTINGS['display_installed'], '', '', 'id="checkDisplayInstalled"') + . ' 
' + .t3lib_BEfunc::getFuncCheck(0, 'SET[display_files]', $this->MOD_SETTINGS['display_files'], '', '', 'id="checkDisplayFiles"') + .' '; + $this->content .= $this->doc->section($LANG->sL('LLL:EXT:lang/locallang_mod_tools_em.xml:header_upd_ext'), $content, 0, 1); + + $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy']; + $timeFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm']; + $content = sprintf($GLOBALS['LANG']->getLL('note_last_update_new'), + date( + $dateFormat . ', ' . $timeFormat, + filemtime(PATH_site . 'typo3temp/extensions.xml.gz') + ) + ) . '
'; + } + + $content .= sprintf($GLOBALS['LANG']->getLL('note_last_update2_new'), + '', ''); + $this->content .= $this->doc->section($LANG->sL('LLL:EXT:lang/locallang_mod_tools_em.xml:header_vers_ret'), $content, 0, 1); + } + + + /** + * Displays a list of extensions where a newer version is available + * in the TER than the one that is installed right now + * integrated from the extension "ter_update_check" for TYPO3 4.2 by Christian Welzel + * + * @return nothing + */ + function showExtensionsToUpdate() { + global $LANG; + $extList = $this->getInstalledExtensions(); + + $content = ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''; + + foreach ($extList[0] as $name => $data) { + $this->xmlhandler->searchExtensionsXMLExact($name, '', '', TRUE, TRUE); + if (!is_array($this->xmlhandler->extensionsXML[$name])) { + continue; + } + + $v = $this->xmlhandler->extensionsXML[$name]['versions']; + $versions = array_keys($v); + natsort($versions); + $lastversion = end($versions); + + if ((t3lib_extMgm::isLoaded($name) || $this->MOD_SETTINGS['display_installed']) && + ($data['EM_CONF']['shy'] == 0 || $this->MOD_SETTINGS['display_shy']) && + $this->versionDifference($lastversion, $data['EM_CONF']['version'], 1)) { + + $imgInfo = @getImageSize($this->getExtPath($name, $data['type']) . '/ext_icon.gif'); + if (is_array($imgInfo)) { + $icon = ''; + } elseif ($data['_ICON']) { //TODO: see if this can be removed, seems to be wrong in this context + $icon = $data['_ICON']; + } else { + $icon = ''; + } + $comment = '
'.$LANG->sL('LLL:EXT:lang/locallang_mod_tools_em.xml:tab_mod_name').''.$LANG->sL('LLL:EXT:lang/locallang_mod_tools_em.xml:tab_mod_key').''.$LANG->sL('LLL:EXT:lang/locallang_mod_tools_em.xml:tab_mod_loc_ver').''.$LANG->sL('LLL:EXT:lang/locallang_mod_tools_em.xml:tab_mod_rem_ver').''.$LANG->sL('LLL:EXT:lang/locallang_mod_tools_em.xml:tab_mod_location').''.$LANG->sL('LLL:EXT:lang/locallang_mod_tools_em.xml:tab_mod_comment').'
'; + foreach ($versions as $vk) { + $va = & $v[$vk]; + if (t3lib_div::int_from_ver($vk) < t3lib_div::int_from_ver($data['EM_CONF']['version'])) { + continue; + } + $comment .= ''.''; + } + $comment .= '
'.$vk.''.nl2br($va[uploadcomment]).'
'; + + $serverMD5Array = $this->serverExtensionMD5Array($name,$data); + if (is_array($serverMD5Array)) { + ksort($serverMD5Array); + } + $currentMD5Array = unserialize($data['EM_CONF']['_md5_values_when_last_written']); + if (is_array($currentMD5Array)) { + @ksort($currentMD5Array); + } + $warn = ''; + if (strcmp(serialize($currentMD5Array), serialize($serverMD5Array))) { + $warn = ''.$GLOBALS['TBE_TEMPLATE']->rfw('
'.$name.': '.$LANG->sL('LLL:EXT:lang/locallang_mod_tools_em.xml:msg_warn_diff').'').''.LF; + if ($this->MOD_SETTINGS['display_files'] == 1) { + $affectedFiles = $this->findMD5ArrayDiff($serverMD5Array,$currentMD5Array); + if (count($affectedFiles)) { + $warn .= ''.$LANG->sL('LLL:EXT:lang/locallang_mod_tools_em.xml:msg_modified').'
'.$GLOBALS['TBE_TEMPLATE']->rfw(implode('
',$affectedFiles)).''.LF; + } + } + } + $content .= ''.$icon.''. +'' . ($data['EM_CONF']['state'] == 'excludeFromUpdates' ? '' . $data['EM_CONF']['title'] . ' ' . $LANG->sL('LLL:EXT:lang/locallang_mod_tools_em.xml:write_protected') . '' : ''.$data[EM_CONF][title].'') . ''. +''.$name.''. +''.$data[EM_CONF][version].''. +''.$lastversion.''. +''.$this->typeLabels[$data['type']].(strlen($data['doubleInstall'])>1?' '.$GLOBALS['TBE_TEMPLATE']->rfw($extInfo['doubleInstall']).'':'').''. +''.$comment.''.LF. +$warn. +'
'.LF; + } + } + + return $content . '
'; + } +} + + +if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/mod/tools/em/index.php']) { + include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/mod/tools/em/index.php']); +} + +?> \ No newline at end of file Index: typo3/sysext/em/mod1/class.em_soap.php =================================================================== --- typo3/sysext/em/mod1/class.em_soap.php (revision 0) +++ typo3/sysext/em/mod1/class.em_soap.php (revision 0) @@ -0,0 +1,413 @@ + +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* A copy is found in the textfile GPL.txt and important notices to the license +* from the author is found in LICENSE.txt distributed with these scripts. +* +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ +/* $Id: class.em_soap.php 7905 2010-06-13 14:42:33Z ohader $ */ + +/** + * Enter description here... + * + */ +class em_soap { + /** + * valid options passed to the constructor : + * wsdl : The WSDL location, can be a local file location or + * an URL. + * soapoptions : Associative array of SOAP options to be passed to + * the SOAP implementation constructor, only used for + * the phpsoap implement. + * authentication : method of authentication : + * 'headers' soap headers are used + * 'prefix' function prefixes are used + * prefix : optional prefix to be put in front of all methods. + * implementation : Which type of soap implementation to use : + * 'detect' automatically detect an implementation. + * 'phpsoap' PHP builtin SOAP module + * + * 'nusoap' NuSOAP class + * + * 'pearsoap' PEAR SOAP class + * + * format : Which type of return structure : + * 'object' PHP objects + * 'array' PHP arrays, default + */ + var $options = array(); + + /** + * SOAP client depending on the available implementations, preferably the PHP SOAP class + * + * @var unknown_type + */ + var $client = false; + var $error = false; + + var $username = false; + var $password = false; + var $reactid = false; + + /** + * Enter description here... + * + * @param array $options + * @param string $username + * @param string $password + * @return [type] ... + */ + function init($options=false, $username=false, $password=false) { + if ($username !== false) { + if ($password === false) { + $this->reactid = $username; + } else { + $this->username = $username; + $this->password = $password; + } + } + + if (!$options['implementation'] || $options['implementation'] == 'detect') { + // Avoid autoloading, since it's only a strategy check here: + if (defined('SOAP_1_2')) { + $options['implementation'] = 'phpsoap'; + } elseif (class_exists('soapclient', false)) { + $options['implementation'] = 'nusoap'; + } elseif (class_exists('SOAP_Client', false)) { + $options['implementation'] = 'pearsoap'; + } + } + + $options['format'] = $options['format'] == 'object' ? 'object' : 'array'; + + if ($options !== false) { + $this->options = (array)$options; + } + + switch ($this->options['implementation']) { + case 'nusoap': + $this->client = new soapclient($this->options['wsdl'], true); + $this->client->getProxy(); + break; + case 'pearsoap': + $this->client = new SOAP_Client($this->options['wsdl'], true); + break; + case 'phpsoap': + $this->client = new SoapClient($options['wsdl'],(array)$options['soapoptions']); + break; + default: + $this->client = false; + } + } + + /** + * Enter description here... + * + * @param string $username + * @param string $password + * @return mixed false on failure, $reactid on success + */ + function login($username, $password) { + $reactid = $this->call('login', array('username' => $username, 'password' => $password)); + + if ($this->error) { + return false; + } + + $this->reactid = $reactid; + $this->username = $username; + $this->password = false; + + return $reactid; + } + + /** + * Enter description here... + * + * @return unknown + */ + function logout() { + $this->call('logout'); + $this->reactid = false; + if ($this->error) { + return false; + } + return true; + } + + + /** + * Enter description here... + * + * @param unknown_type $func + * @param unknown_type $param + * @param unknown_type $username + * @param unknown_type $password + * @return unknown + */ + function call($func, $param=array(), $username=false, $password=false) { + if (!$this->client) { + $this->error = "Error in Webservices.class.php: No soap client implementation found. ". + "Make sure a SOAP library such as 'NuSoap.php' is included."; + return false; + } + + if ($username !== false) { + if ($password === false) { + $this->reactid = $username; + } else { + $this->username = $username; + $this->password = $password; + } + } + + if ($this->options['authentication'] == 'prefix') { + $param = array_merge(array('reactid' => $this->reactid), $param); + } + + if ($this->options['prefix']) { + $func = $this->options['prefix'].ucfirst($func); + } + + $this->error = false; + + switch ($this->options['implementation']) { + case 'nusoap' : return $this->callNuSOAP($func, $param); break; + case 'pearsoap' : return $this->callPearSOAP($func, $param); break; + case 'phpsoap' : return $this->callPhpSOAP($func, $param); break; + } + + return false; + } + + /** + * Enter description here... + * + * @param unknown_type $func + * @param unknown_type $param + * @return unknown + */ + function callPhpSOAP($func, $param) { + $header = null; + if ($this->options['authentication'] == 'headers') { + if ($this->reactid) { + $header = new SoapHeader( + '','HeaderAuthenticate', + (object)array('reactid' => $this->reactid), 1 + ); + } elseif ($this->username && $this->password) { + $header = new SoapHeader( + '','HeaderLogin', + (object)array( + 'username' => $this->username, + 'password' => $this->password + ), 1 + ); + $this->password = false; + } + } + + $result = $this->client->__soapCall($func, $param, NULL, $header); + + if (is_soap_fault($result)) { + $this->error = $result; + return false; + } + + if (is_a($this->client->headersIn['HeaderAuthenticate'],'stdClass')) { + $this->reactid = $this->client->headersIn['HeaderAuthenticate']->reactid; + } + + return $this->options['format'] == 'object' ? $result : $this->object2array($result); + } + + /** + * Enter description here... + * + * @param unknown_type $func + * @param unknown_type $param + * @return unknown + */ + function callPearSOAP($func,$param) { + if ($this->options['authentication'] == 'headers') { + if ($this->reactid) { + $this->client->addHeader( + new SOAP_Header( + 'HeaderAuthenticate', NULL, + array('reactid' => $this->reactid), 1 + ) + ); + } elseif ($this->username && $this->password) { + $this->client->addHeader( + new SOAP_Header( + 'HeaderLogin', NULL, + array( + 'username' => $this->username, + 'password' => $this->password + ), 1 + ) + ); + $this->password = false; + } + } + + + $result = $this->client->call($func, $param); + + if (PEAR::isError($result)) { + $this->error = $result; + return false; + } + + if (is_a($this->client->headersIn['HeaderAuthenticate'],'stdClass')) { + $this->reactid = $this->client->headersIn['HeaderAuthenticate']->reactid; + } + + return $this->options['format'] == 'object' ? $result : $this->object2array($result); + } + + /** + * Enter description here... + * + * @param unknown_type $func + * @param unknown_type $param + * @return unknown + */ + function callNuSOAP($func,$param) { + $header = false; + if ($this->options['authentication'] == 'headers') { + if ($this->reactid) { + $header = ( + "". + "".htmlspecialchars($this->reactid)."". + "" + ); + } elseif ($this->username && $this->password) { + $header = ( + "". + "".htmlspecialchars($this->username)."". + "".htmlspecialchars($this->password)."". + "" //HeaderLogin + ); + $this->password = false; + } + } + + $result = $this->client->call($func, $param, false, false, $header); + + if ($this->error = $this->client->getError()) { + return false; + } + + // nusoap header support is very limited + $headers = $this->client->getHeaders(); + $matches = array(); + if (preg_match('~<([a-z0-9]+:)?reactid[^>]*>([^<]*)~is', $headers, $matches)) { + $this->reactid = $matches[2]; + } + + return $this->options['format'] == 'object' ? $this->array2object($result) : $result; + } + + /** + * Enter description here... + * + * @param unknown_type $object + * @return unknown + */ + function object2array($object) { + if (!is_object($object) && !is_array($object)) { + return $object; + } + + $array = (array)$object; + foreach ($array as $key => $value) { + $array[$key] = $this->object2array($value); + } + return $array; + } + + /** + * Enter description here... + * + * @param unknown_type $array + * @return unknown + */ + function array2object($array) { + if (!is_array($array)) { + return $array; + } + + foreach ($array as $key => $value) { + $array[$key] = $this->array2object($value); + } + return (object)$array; + } + + /** + * Enter description here... + * + * @return unknown + */ + function lastRequest() { + switch ($this->options['implementation']) { + case 'nusoap' : return $this->client->request; break; + case 'pearsoap' : return $this->client->__getlastrequest(); break; + case 'phpsoap' : return $this->client->__getLastRequest(); break; + } + + return false; + } + + /** + * Enter description here... + * + * @return unknown + */ + function lastResponse() { + switch ($this->options['implementation']) { + case 'nusoap' : return $this->client->response; break; + case 'pearsoap' : return $this->client->__getlastresponse(); break; + case 'phpsoap' : return $this->client->__getLastResponse(); break; + } + + return false; + } + + /** + * Enter description here... + * + * @return unknown + */ + function getFunctions() { + switch ($this->options['implementation']) { + case 'nusoap' : return array_keys($this->client->operations); break; + case 'pearsoap' : return false; break; + case 'phpsoap' : return $this->client->__getFunctions(); break; + } + + return false; + } +} + +?> \ No newline at end of file Index: typo3/sysext/em/mod1/class.em_terconnection.php =================================================================== --- typo3/sysext/em/mod1/class.em_terconnection.php (revision 0) +++ typo3/sysext/em/mod1/class.em_terconnection.php (revision 0) @@ -0,0 +1,331 @@ + +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* A copy is found in the textfile GPL.txt and important notices to the license +* from the author is found in LICENSE.txt distributed with these scripts. +* +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ + + +if (!defined('SOAP_1_2')) { + require_once('class.nusoap.php'); +# require_once('/usr/share/php/SOAP/Client.php'); +} +require_once('class.em_soap.php'); + +/** + * TER2 connection handling class for the TYPO3 Extension Manager. + * + * It contains methods for downloading and uploading extensions and related code + * + * @author Karsten Dambekalns + * @author Kasper Skaarhoj + * @package TYPO3 + * @subpackage EM + */ +class SC_mod_tools_em_terconnection { + var $wsdlURL; + + /** + * Extension manager module + * + * @var SC_mod_tools_em_index + */ + var $emObj; + + /** + * Fetches an extension from the given mirror + * + * @param string $extKey Extension Key + * @param string $version Version to install + * @param string $expectedMD5 Expected MD5 hash of extension file + * @param string $mirrorURL URL of mirror to use + * @return mixed T3X data (array) or error message (string) + */ + function fetchExtension($extKey, $version, $expectedMD5, $mirrorURL) { + $extPath = t3lib_div::strtolower($extKey); + $mirrorURL .= $extPath{0} . '/' . $extPath{1} . '/' . $extPath . '_' . $version . '.t3x'; + $t3x = t3lib_div::getURL($mirrorURL, 0, array(TYPO3_user_agent)); + $MD5 = md5($t3x); + + if($t3x===false) return 'The T3X file could not be fetched. Possible reasons: network problems, allow_url_fopen is off, curl is not enabled in Install tool.'; + + if($MD5 == $expectedMD5) { + // Fetch and return: + return $this->decodeExchangeData($t3x); + } else { + return 'Error: MD5 hash of downloaded file not as expected:
'.$MD5.' != '.$expectedMD5; + } + } + + /** + * Fetches an extensions l10n file from the given mirror + * + * @param string $extKey Extension Key + * @param string $lang The language code of the translation to fetch + * @param string $mirrorURL URL of mirror to use + * @return mixed Array containing l10n data or error message (string) + */ + function fetchTranslation($extKey, $lang, $mirrorURL) { + $extPath = t3lib_div::strtolower($extKey); + $mirrorURL .= $extPath{0} . '/' . $extPath{1} . '/' . $extPath . '-l10n/' . $extPath . '-l10n-' . $lang . '.zip'; + $l10n = t3lib_div::getURL($mirrorURL, 0, array(TYPO3_user_agent)); + + if($l10n !== false) { + return array($l10n); + } else { + return 'Error: Translation could not be fetched.'; + } + } + + /** + * Fetches extension l10n status from the given mirror + * + * @param string $extKey Extension Key + * @param string $mirrorURL URL of mirror to use + * @return mixed Array containing l10n status data or FALSE if no status could be fetched + */ + function fetchTranslationStatus($extKey, $mirrorURL) { + $extPath = t3lib_div::strtolower($extKey); + $mirrorURL .= $extPath{0} . '/' . $extPath{1} . '/' . $extPath . '-l10n/' . $extPath . '-l10n.xml'; + $remote = t3lib_div::getURL($mirrorURL, 0, array(TYPO3_user_agent)); + + if($remote !== false) { + $parsed = $this->emObj->xmlhandler->parseL10nXML($remote); + return $parsed['languagePackIndex']; + } + + return FALSE; + } + + /** + * Decode server data + * This is information like the extension list, extension information etc., return data after uploads (new em_conf) + * + * @param string Data stream from remove server + * @return mixed On success, returns an array with data array and stats array as key 0 and 1. Otherwise returns error string + * @see fetchServerData(), processRepositoryReturnData() + */ + function decodeServerData($externalData) { + $parts = explode(':',$externalData,4); + $dat = base64_decode($parts[2]); + // compare hashes ignoring any leading whitespace. See bug #0000365. + if (ltrim($parts[0])==md5($dat)) { + if ($parts[1]=='gzcompress') { + if (function_exists('gzuncompress')) { + $dat = gzuncompress($dat); + } else return 'Decoding Error: No decompressor available for compressed content. gzuncompress() function is not available!'; + } + $listArr = unserialize($dat); + + if (is_array($listArr)) { + return $listArr; + } else { + return 'Error: Unserialized information was not an array - strange!'; + } + } else return 'Error: MD5 hashes in T3X data did not match!'; + } + + /** + * Decodes extension upload array. + * This kind of data is when an extension is uploaded to TER + * + * @param string Data stream + * @return mixed Array with result on success, otherwise an error string. + */ + function decodeExchangeData($str) { + $parts = explode(':',$str,3); + if ($parts[1]=='gzcompress') { + if (function_exists('gzuncompress')) { + $parts[2] = gzuncompress($parts[2]); + } else return 'Decoding Error: No decompressor available for compressed content. gzcompress()/gzuncompress() functions are not available!'; + } + if (md5($parts[2]) == $parts[0]) { + $output = unserialize($parts[2]); + if (is_array($output)) { + return array($output,''); + } else return 'Error: Content could not be unserialized to an array. Strange (since MD5 hashes match!)'; + } else return 'Error: MD5 mismatch. Maybe the extension file was downloaded and saved as a text file by the browser and thereby corrupted!? (Always select "All" filetype when saving extensions)'; + } + + + /** + * Encodes extension upload array + * + * @param array Array containing extension + * @return string Content stream + */ + function makeUploadDataFromArray($uploadArray) { + if (is_array($uploadArray)) { + $serialized = serialize($uploadArray); + $md5 = md5($serialized); + + $content = $md5.':'; + $content.= 'gzcompress:'; + $content.= gzcompress($serialized); + } + return $content; + } + + /** + * [Describe function...] + * + * @param [type] $em: ... + * @return [type] ... + */ + function uploadToTER($em) { + $uArr = $this->emObj->makeUploadArray($em['extKey'],$em['extInfo']); + if(!is_array($uArr)) return $uArr; + + // Render new version number: + $newVersionBase = $em['extInfo']['EM_CONF']['version']; + switch((string)$em['upload']['mode']) { + case 'new_dev': + $cmd='dev'; + break; + case 'new_sub': + $cmd='sub'; + break; + case 'new_main': + $cmd='main'; + break; + case 'custom': + $newVersionBase = $em['upload']['version']; + case 'latest': + default: + $cmd=''; + break; + } + $versionArr = $this->emObj->renderVersion($newVersionBase, $cmd); + $em['version'] = $versionArr['version']; + + // Create dependency / conflict information: + $dependenciesArr = array (); + $extKeysArr = $uArr['EM_CONF']['constraints']['depends']; + + if (is_array($extKeysArr)) { + foreach ($extKeysArr as $extKey => $version) { + if (strlen($extKey)) { + $dependenciesArr[] = array ( + 'kind' => 'depends', + 'extensionKey' => utf8_encode($extKey), + 'versionRange' => utf8_encode($version), + ); + } + } + } + + $extKeysArr = $uArr['EM_CONF']['constraints']['conflicts']; + if (is_array($extKeysArr)) { + foreach ($extKeysArr as $extKey => $version) { + if (strlen($extKey)) { + $dependenciesArr[] = array ( + 'kind' => 'conflicts', + 'extensionKey' => utf8_encode($extKey), + 'versionRange' => utf8_encode($version), + ); + } + } + } + // FIXME: This part must be removed, when the problem is solved on the TER-Server #5919 + if (count($dependenciesArr) == 1) { + $dependenciesArr[] = array ( + 'kind' => 'depends', + 'extensionKey' => '', + 'versionRange' => '', + ); + } + // END for Bug #5919 + + // Compile data for SOAP call: + $accountData = array( + 'username' => $em['user']['fe_u'], + 'password' => $em['user']['fe_p'] + ); + $extensionData = array ( + 'extensionKey' => utf8_encode($em['extKey']), + 'version' => utf8_encode($em['version']), + 'metaData' => array ( + 'title' => utf8_encode($uArr['EM_CONF']['title']), + 'description' => utf8_encode($uArr['EM_CONF']['description']), + 'category' => utf8_encode($uArr['EM_CONF']['category']), + 'state' => utf8_encode($uArr['EM_CONF']['state']), + 'authorName' => utf8_encode($uArr['EM_CONF']['author']), + 'authorEmail' => utf8_encode($uArr['EM_CONF']['author_email']), + 'authorCompany' => utf8_encode($uArr['EM_CONF']['author_company']), + ), + 'technicalData' => array ( + 'dependencies' => $dependenciesArr, + 'loadOrder' => utf8_encode($uArr['EM_CONF']['loadOrder']), + 'uploadFolder' => (boolean) intval($uArr['EM_CONF']['uploadfolder']), + 'createDirs' => utf8_encode($uArr['EM_CONF']['createDirs']), + 'shy' => (boolean) intval($uArr['EM_CONF']['shy']), + 'modules' => utf8_encode($uArr['EM_CONF']['module']), + 'modifyTables' => utf8_encode($uArr['EM_CONF']['modify_tables']), + 'priority' => utf8_encode($uArr['EM_CONF']['priority']), + 'clearCacheOnLoad' => (boolean) intval($uArr['EM_CONF']['clearCacheOnLoad']), + 'lockType' => utf8_encode($uArr['EM_CONF']['lockType']), + ), + 'infoData' => array( + 'codeLines' => intval($uArr['misc']['codelines']), + 'codeBytes' => intval($uArr['misc']['codebytes']), + 'codingGuidelinesCompliance' => utf8_encode($uArr['EM_CONF']['CGLcompliance']), + 'codingGuidelinesComplianceNotes' => utf8_encode($uArr['EM_CONF']['CGLcompliance_note']), + 'uploadComment' => utf8_encode($em['upload']['comment']), + 'techInfo' => $uArr['techInfo'], + ), + ); + + $filesData = array(); + foreach ($uArr['FILES'] as $filename => $infoArr) { + // Avoid autoloading "soapclient", since it's only a strategy check here: + $content = (!defined('SOAP_1_2') && class_exists('soapclient', false)) ? base64_encode($infoArr['content']) : $infoArr['content']; // bug in NuSOAP - no automatic encoding + $filesData[] = array ( + 'name' => utf8_encode($infoArr['name']), + 'size' => intval($infoArr['size']), + 'modificationTime' => intval($infoArr['mtime']), + 'isExecutable' => intval($infoArr['is_executable']), + 'content' => $content, + 'contentMD5' => $infoArr['content_md5'], + ); + } + + $soap = t3lib_div::makeInstance('em_soap'); + $soap->init(array('wsdl'=>$this->wsdlURL,'soapoptions'=> array('trace'=>1,'exceptions'=>0))); + $response = $soap->call('uploadExtension', array('accountData' => $accountData, 'extensionData' => $extensionData, 'filesData' => $filesData)); + + if($response===false) { + switch(true) { + case is_string($soap->error): + return $soap->error; + break; + default: + return $soap->error->faultstring; + } + } + + return $response; + } +} + +?> \ No newline at end of file Index: typo3/sysext/em/mod1/class.em_unzip.php =================================================================== --- typo3/sysext/em/mod1/class.em_unzip.php (revision 0) +++ typo3/sysext/em/mod1/class.em_unzip.php (revision 0) @@ -0,0 +1,1354 @@ + +* (c) 2005-2010 Karsten Dambekalns +* All rights reserved +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +* MA 02110-1301 USA +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ +/** + * Module: Extension manager + * + * $Id: class.em_unzip.php 7905 2010-06-13 14:42:33Z ohader $ + * + * @author Vincent Blavet + * @author Karsten Dambekalns + */ + + +// Constants +define( 'ARCHIVE_ZIP_READ_BLOCK_SIZE', 2048 ); + +// File list separator +define( 'ARCHIVE_ZIP_SEPARATOR', ',' ); + +define( 'ARCHIVE_ZIP_TEMPORARY_DIR', '' ); + +// Error codes +define( 'ARCHIVE_ZIP_ERR_NO_ERROR', 0 ); +define( 'ARCHIVE_ZIP_ERR_WRITE_OPEN_FAIL', -1 ); +define( 'ARCHIVE_ZIP_ERR_READ_OPEN_FAIL', -2 ); +define( 'ARCHIVE_ZIP_ERR_INVALID_PARAMETER', -3 ); +define( 'ARCHIVE_ZIP_ERR_MISSING_FILE', -4 ); +define( 'ARCHIVE_ZIP_ERR_FILENAME_TOO_LONG', -5 ); +define( 'ARCHIVE_ZIP_ERR_INVALID_ZIP', -6 ); +define( 'ARCHIVE_ZIP_ERR_BAD_EXTRACTED_FILE', -7 ); +define( 'ARCHIVE_ZIP_ERR_DIR_CREATE_FAIL', -8 ); +define( 'ARCHIVE_ZIP_ERR_BAD_EXTENSION', -9 ); +define( 'ARCHIVE_ZIP_ERR_BAD_FORMAT', -10 ); +define( 'ARCHIVE_ZIP_ERR_DELETE_FILE_FAIL', -11 ); +define( 'ARCHIVE_ZIP_ERR_RENAME_FILE_FAIL', -12 ); +define( 'ARCHIVE_ZIP_ERR_BAD_CHECKSUM', -13 ); +define( 'ARCHIVE_ZIP_ERR_INVALID_ARCHIVE_ZIP', -14 ); +define( 'ARCHIVE_ZIP_ERR_MISSING_OPTION_VALUE', -15 ); +define( 'ARCHIVE_ZIP_ERR_INVALID_PARAM_VALUE', -16 ); + +// Warning codes +define( 'ARCHIVE_ZIP_WARN_NO_WARNING', 0 ); +define( 'ARCHIVE_ZIP_WARN_FILE_EXIST', 1 ); + +// Methods parameters +define( 'ARCHIVE_ZIP_PARAM_PATH', 'path' ); +define( 'ARCHIVE_ZIP_PARAM_ADD_PATH', 'add_path' ); +define( 'ARCHIVE_ZIP_PARAM_REMOVE_PATH', 'remove_path' ); +define( 'ARCHIVE_ZIP_PARAM_REMOVE_ALL_PATH', 'remove_all_path' ); +define( 'ARCHIVE_ZIP_PARAM_SET_CHMOD', 'set_chmod' ); +define( 'ARCHIVE_ZIP_PARAM_EXTRACT_AS_STRING', 'extract_as_string' ); +define( 'ARCHIVE_ZIP_PARAM_NO_COMPRESSION', 'no_compression' ); + +define( 'ARCHIVE_ZIP_PARAM_PRE_EXTRACT', 'callback_pre_extract' ); +define( 'ARCHIVE_ZIP_PARAM_POST_EXTRACT', 'callback_post_extract' ); +define( 'ARCHIVE_ZIP_PARAM_PRE_ADD', 'callback_pre_add' ); +define( 'ARCHIVE_ZIP_PARAM_POST_ADD', 'callback_post_add' ); + + + +/** +* Class for unpacking zip archive files +* +* @author Vincent Blavet +* @author Karsten Dambekalns +*/ +class em_unzip { + /** + * The filename of the zip archive. + * + * @var string Name of the Zip file + */ + var $_zipname=''; + + /** + * File descriptor of the opened Zip file. + * + * @var int Internal zip file descriptor + */ + var $_zip_fd=0; + + /** + * @var int last error code + */ + var $_error_code=1; + + /** + * @var string Last error description + */ + var $_error_string=''; + + /** + * em_unzip Class constructor. This flavour of the constructor only + * declare a new em_unzip object, identifying it by the name of the + * zip file. + * + * @param string $p_zipname The name of the zip archive to create + * @access public + */ + function em_unzip($p_zipname) { + + // Check the zlib + if (!extension_loaded('zlib')) { + throw new RuntimeException( + 'TYPO3 Fatal Error: ' . "The extension 'zlib' couldn't be found.\n" . + "Please make sure your version of PHP was built " . + "with 'zlib' support.\n", + 1270853984 + ); + } + + // Set the attributes + $this->_zipname = $p_zipname; + $this->_zip_fd = 0; + + return; + } + + + + /** + * This method extract the files and folders which are in the zip archive. + * It can extract all the archive or a part of the archive by using filter + * feature (extract by name, by index, by ereg, by preg). The extraction + * can occur in the current path or an other path. + * All the advanced features are activated by the use of variable + * parameters. + * The return value is an array of entry descriptions which gives + * information on extracted files (See listContent()). + * The method may return a success value (an array) even if some files + * are not correctly extracted (see the file status in listContent()). + * The supported variable parameters for this method are : + * 'add_path' : Path where the files and directories are to be extracted + * + * @access public + * @param mixed $p_params An array of variable parameters and values. + * @return mixed An array of file description on success, + * 0 on an unrecoverable failure, an error code is logged. + */ + function extract($p_params=0) { + $this->_errorReset(); + + // Check archive + if (!$this->_checkFormat()) { + return(0); + } + + // Set default values + if ($p_params === 0) { + $p_params = array(); + } + if ($this->_check_parameters($p_params, + array ('extract_as_string' => false, + 'add_path' => '', + 'remove_path' => '', + 'remove_all_path' => false, + 'callback_pre_extract' => '', + 'callback_post_extract' => '', + 'set_chmod' => 0) ) != 1) { + return 0; + } + + // Call the extracting fct + $v_list = array(); + if ($this->_extractByRule($v_list, $p_params) != 1) { + unset($v_list); + return(0); + } + + return $v_list; + } + + /** + * Method that gives the lastest error code. + * + * @access public + * @return integer The error code value. + */ + function errorCode() { + return($this->_error_code); + } + + /** + * This method gives the latest error code name. + * + * @access public + * @param boolean $p_with_code If true, gives the name and the int value. + * @return string The error name. + */ + function errorName($p_with_code=false) { + $v_const_list = get_defined_constants(); + + // Extract error constants from all const. + for (reset($v_const_list); + list($v_key, $v_value) = each($v_const_list);) { + if (substr($v_key, 0, strlen('ARCHIVE_ZIP_ERR_')) =='ARCHIVE_ZIP_ERR_') { + $v_error_list[$v_key] = $v_value; + } + } + + // Search the name form the code value + $v_key=array_search($this->_error_code, $v_error_list, true); + if ($v_key!=false) { + $v_value = $v_key; + } else { + $v_value = 'NoName'; + } + + if ($p_with_code) { + return($v_value.' ('.$this->_error_code.')'); + } else { + return($v_value); + } + } + + /** + * This method returns the description associated with the latest error. + * + * @access public + * @param boolean $p_full If set to true gives the description with the + * error code, the name and the description. + * If set to false gives only the description + * and the error code. + * @return string The error description. + */ + function errorInfo($p_full=false) { + if ($p_full) { + return($this->errorName(true)." : ".$this->_error_string); + } else { + return($this->_error_string." [code ".$this->_error_code."]"); + } + } + + + /** + * em_unzip::_checkFormat() + * + * { Description } + * + * @param integer $p_level + */ + function _checkFormat($p_level=0) { + $v_result = true; + + // Reset the error handler + $this->_errorReset(); + + // Look if the file exits + if (!is_file($this->_zipname)) { + // Error log + $this->_errorLog(ARCHIVE_ZIP_ERR_MISSING_FILE, + "Missing archive file '".$this->_zipname."'"); + return(false); + } + + // Check that the file is readeable + if (!is_readable($this->_zipname)) { + // Error log + $this->_errorLog(ARCHIVE_ZIP_ERR_READ_OPEN_FAIL, + "Unable to read archive '".$this->_zipname."'"); + return(false); + } + + // Check the magic code + // TBC + + // Check the central header + // TBC + + // Check each file header + // TBC + + // Return + return $v_result; + } + + + /** + * em_unzip::_openFd() + * + * { Description } + * + */ + function _openFd($p_mode) { + $v_result=1; + + // Look if already open + if ($this->_zip_fd != 0) { + $this->_errorLog(ARCHIVE_ZIP_ERR_READ_OPEN_FAIL, + 'Zip file \''.$this->_zipname.'\' already open'); + return em_unzip::errorCode(); + } + + // Open the zip file + if (($this->_zip_fd = @fopen($this->_zipname, $p_mode)) == 0) { + $this->_errorLog(ARCHIVE_ZIP_ERR_READ_OPEN_FAIL, + 'Unable to open archive \''.$this->_zipname + .'\' in '.$p_mode.' mode'); + return em_unzip::errorCode(); + } + + // Return + return $v_result; + } + + /** + * em_unzip::_closeFd() + * + * { Description } + * + */ + function _closeFd() { + $v_result=1; + + if ($this->_zip_fd != 0) + @fclose($this->_zip_fd); + $this->_zip_fd = 0; + + // Return + return $v_result; + } + + + + /** + * em_unzip::_convertHeader2FileInfo() + * + * { Description } + * + */ + function _convertHeader2FileInfo($p_header, &$p_info) { + $v_result=1; + + // Get the interesting attributes + $p_info['filename'] = $p_header['filename']; + $p_info['stored_filename'] = $p_header['stored_filename']; + $p_info['size'] = $p_header['size']; + $p_info['compressed_size'] = $p_header['compressed_size']; + $p_info['mtime'] = $p_header['mtime']; + $p_info['comment'] = $p_header['comment']; + $p_info['folder'] = (($p_header['external']&0x00000010)==0x00000010); + $p_info['index'] = $p_header['index']; + $p_info['status'] = $p_header['status']; + + // Return + return $v_result; + } + + + // Function : _extractByRule() + // Description : + // Extract a file or directory depending of rules (by index, by name, ...) + // Parameters : + // $p_file_list : An array where will be placed the properties of each + // extracted file + // $p_path : Path to add while writing the extracted files + // $p_remove_path : Path to remove (from the file memorized path) while writing the + // extracted files. If the path does not match the file path, + // the file is extracted with its memorized path. + // $p_remove_path does not apply to 'list' mode. + // $p_path and $p_remove_path are commulative. + // Return Values : + // 1 on success,0 or less on error (see error code list) + + /** + * em_unzip::_extractByRule() + * + * { Description } + * + */ + function _extractByRule(&$p_file_list, &$p_params) + { + $v_result=1; + + $p_path = $p_params['add_path']; + $p_remove_path = $p_params['remove_path']; + $p_remove_all_path = $p_params['remove_all_path']; + + // Check the path + if (($p_path == "") + || ((substr($p_path, 0, 1) != "/") + && (substr($p_path, 0, 3) != "../") && (substr($p_path,1,2)!=":/"))) + $p_path = "./".$p_path; + + // Reduce the path last (and duplicated) '/' + if (($p_path != "./") && ($p_path != "/")) { + // Look for the path end '/' + while (substr($p_path, -1) == "/") { + $p_path = substr($p_path, 0, strlen($p_path)-1); + } + } + + // Open the zip file + if (($v_result = $this->_openFd('rb')) != 1) + { + return $v_result; + } + + // Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->_readEndCentralDir($v_central_dir)) != 1) + { + // Close the zip file + $this->_closeFd(); + + return $v_result; + } + + // Start at beginning of Central Dir + $v_pos_entry = $v_central_dir['offset']; + + // Read each entry + $j_start = 0; + for ($i=0, $v_nb_extracted=0; $i<$v_central_dir['entries']; $i++) { + // Read next Central dir entry + @rewind($this->_zip_fd); + if (@fseek($this->_zip_fd, $v_pos_entry)) { + $this->_closeFd(); + + $this->_errorLog(ARCHIVE_ZIP_ERR_INVALID_ARCHIVE_ZIP, + 'Invalid archive size'); + + return em_unzip::errorCode(); + } + + // Read the file header + $v_header = array(); + if (($v_result = $this->_readCentralFileHeader($v_header)) != 1) { + $this->_closeFd(); + + return $v_result; + } + + // Store the index + $v_header['index'] = $i; + + // Store the file position + $v_pos_entry = ftell($this->_zip_fd); + + + // Go to the file position + @rewind($this->_zip_fd); + if (@fseek($this->_zip_fd, $v_header['offset'])) + { + // Close the zip file + $this->_closeFd(); + + // Error log + $this->_errorLog(ARCHIVE_ZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + + // Return + return em_unzip::errorCode(); + } + + // Extracting the file + if (($v_result = $this->_extractFile($v_header, $p_path, $p_remove_path, $p_remove_all_path, $p_params)) != 1) + { + // Close the zip file + $this->_closeFd(); + + return $v_result; + } + + // Get the only interesting attributes + if (($v_result = $this->_convertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1) + { + // Close the zip file + $this->_closeFd(); + + return $v_result; + } + } + + // Close the zip file + $this->_closeFd(); + + // Return + return $v_result; + } + + /** + * em_unzip::_extractFile() + * + * { Description } + * + */ + function _extractFile(&$p_entry, $p_path, $p_remove_path, $p_remove_all_path, &$p_params) { + $v_result=1; + + // Read the file header + $v_header = ''; + if (($v_result = $this->_readFileHeader($v_header)) != 1) + { + // Return + return $v_result; + } + + + // Check that the file header is coherent with $p_entry info + // TBC + + // Look for all path to remove + if ($p_remove_all_path == true) { + // Get the basename of the path + $p_entry['filename'] = basename($p_entry['filename']); + } + + // Look for path to remove + else if ($p_remove_path != "") + { + //if (strcmp($p_remove_path, $p_entry['filename'])==0) + if ($this->_tool_PathInclusion($p_remove_path, $p_entry['filename']) == 2) + { + + // Change the file status + $p_entry['status'] = "filtered"; + + // Return + return $v_result; + } + + $p_remove_path_size = strlen($p_remove_path); + if (substr($p_entry['filename'], 0, $p_remove_path_size) == $p_remove_path) + { + + // Remove the path + $p_entry['filename'] = substr($p_entry['filename'], $p_remove_path_size); + + } + } + + // Add the path + if ($p_path != '') + { + $p_entry['filename'] = $p_path."/".$p_entry['filename']; + } + + // Look for pre-extract callback + if ( (isset($p_params[ARCHIVE_ZIP_PARAM_PRE_EXTRACT])) + && ($p_params[ARCHIVE_ZIP_PARAM_PRE_EXTRACT] != '')) { + + // Generate a local information + $v_local_header = array(); + $this->_convertHeader2FileInfo($p_entry, $v_local_header); + + // Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + eval('$v_result = '.$p_params[ARCHIVE_ZIP_PARAM_PRE_EXTRACT].'(ARCHIVE_ZIP_PARAM_PRE_EXTRACT, $v_local_header);'); + if ($v_result == 0) { + // Change the file status + $p_entry['status'] = "skipped"; + $v_result = 1; + } + + // Update the informations + // Only some fields can be modified + $p_entry['filename'] = $v_local_header['filename']; + } + + // Trace + + // Look if extraction should be done + if ($p_entry['status'] == 'ok') { + + // Look for specific actions while the file exist + if (file_exists($p_entry['filename'])) { + // Look if file is a directory + if (is_dir($p_entry['filename'])) { + // Change the file status + $p_entry['status'] = "already_a_directory"; + + // Return + //return $v_result; + } + // Look if file is write protected + else if (!is_writeable($p_entry['filename'])) { + // Change the file status + $p_entry['status'] = "write_protected"; + + // Return + //return $v_result; + } + + // Look if the extracted file is older + else if (filemtime($p_entry['filename']) > $p_entry['mtime']) { + // Change the file status + $p_entry['status'] = "newer_exist"; + + // Return + //return $v_result; + } + } + + // Check the directory availability and create it if necessary + else { + if ((($p_entry['external']&0x00000010)==0x00000010) || (substr($p_entry['filename'], -1) == '/')) + $v_dir_to_check = $p_entry['filename']; + else if (!strstr($p_entry['filename'], "/")) + $v_dir_to_check = ""; + else + $v_dir_to_check = dirname($p_entry['filename']); + + if (($v_result = $this->_dirCheck($v_dir_to_check, (($p_entry['external']&0x00000010)==0x00000010))) != 1) { + // Change the file status + $p_entry['status'] = "path_creation_fail"; + + // Return + //return $v_result; + $v_result = 1; + } + } + } + + // Look if extraction should be done + if ($p_entry['status'] == 'ok') { + // Do the extraction (if not a folder) + if (!(($p_entry['external']&0x00000010)==0x00000010)) { + // Look for not compressed file + if ($p_entry['compressed_size'] == $p_entry['size']) { + // Opening destination file + if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) { + // Change the file status + $p_entry['status'] = "write_error"; + + // Return + return $v_result; + } + + + // Read the file by ARCHIVE_ZIP_READ_BLOCK_SIZE octets blocks + $v_size = $p_entry['compressed_size']; + while ($v_size != 0) { + $v_read_size = ($v_size < ARCHIVE_ZIP_READ_BLOCK_SIZE ? $v_size : ARCHIVE_ZIP_READ_BLOCK_SIZE); + $v_buffer = fread($this->_zip_fd, $v_read_size); + $v_binary_data = pack('a'.$v_read_size, $v_buffer); + @fwrite($v_dest_file, $v_binary_data, $v_read_size); + $v_size -= $v_read_size; + } + + // Closing the destination file + fclose($v_dest_file); + + // Change the file mtime + touch($p_entry['filename'], $p_entry['mtime']); + } else { + // Trace + + // Opening destination file + if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) { + + // Change the file status + $p_entry['status'] = "write_error"; + + return $v_result; + } + + + // Read the compressed file in a buffer (one shot) + $v_buffer = @fread($this->_zip_fd, $p_entry['compressed_size']); + + // Decompress the file + $v_file_content = gzinflate($v_buffer); + unset($v_buffer); + + // Write the uncompressed data + @fwrite($v_dest_file, $v_file_content, $p_entry['size']); + unset($v_file_content); + + // Closing the destination file + @fclose($v_dest_file); + + // Change the file mtime + @touch($p_entry['filename'], $p_entry['mtime']); + } + + // Look for chmod option + if ( (isset($p_params[ARCHIVE_ZIP_PARAM_SET_CHMOD])) + && ($p_params[ARCHIVE_ZIP_PARAM_SET_CHMOD] != 0)) { + + // Change the mode of the file + chmod($p_entry['filename'], $p_params[ARCHIVE_ZIP_PARAM_SET_CHMOD]); + } + + } + } + + // Look for post-extract callback + if ( (isset($p_params[ARCHIVE_ZIP_PARAM_POST_EXTRACT])) + && ($p_params[ARCHIVE_ZIP_PARAM_POST_EXTRACT] != '')) { + + // Generate a local information + $v_local_header = array(); + $this->_convertHeader2FileInfo($p_entry, $v_local_header); + + // Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + eval('$v_result = '.$p_params[ARCHIVE_ZIP_PARAM_POST_EXTRACT].'(ARCHIVE_ZIP_PARAM_POST_EXTRACT, $v_local_header);'); + } + + // Return + return $v_result; + } + + /** + * em_unzip::_readFileHeader() + * + * { Description } + * + */ + function _readFileHeader(&$p_header) { + $v_result=1; + + // Read the 4 bytes signature + $v_binary_data = @fread($this->_zip_fd, 4); + $v_data = unpack('Vid', $v_binary_data); + + // Check signature + if ($v_data['id'] != 0x04034b50) { + + // Error log + $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, 'Invalid archive structure'); + + // Return + return em_unzip::errorCode(); + } + + // Read the first 42 bytes of the header + $v_binary_data = fread($this->_zip_fd, 26); + + // Look for invalid block size + if (strlen($v_binary_data) != 26) { + $p_header['filename'] = ""; + $p_header['status'] = "invalid_header"; + + // Error log + $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, "Invalid block size : ".strlen($v_binary_data)); + + // Return + return em_unzip::errorCode(); + } + + // Extract the values + $v_data = unpack('vversion/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len', $v_binary_data); + + // Get filename + $p_header['filename'] = fread($this->_zip_fd, $v_data['filename_len']); + + // Get extra_fields + if ($v_data['extra_len'] != 0) { + $p_header['extra'] = fread($this->_zip_fd, $v_data['extra_len']); + } + else { + $p_header['extra'] = ''; + } + + // Extract properties + $p_header['compression'] = $v_data['compression']; + $p_header['size'] = $v_data['size']; + $p_header['compressed_size'] = $v_data['compressed_size']; + $p_header['crc'] = $v_data['crc']; + $p_header['flag'] = $v_data['flag']; + + // Recuperate date in UNIX format + $p_header['mdate'] = $v_data['mdate']; + $p_header['mtime'] = $v_data['mtime']; + if ($p_header['mdate'] && $p_header['mtime']) { + // Extract time + $v_hour = ($p_header['mtime'] & 0xF800) >> 11; + $v_minute = ($p_header['mtime'] & 0x07E0) >> 5; + $v_seconde = ($p_header['mtime'] & 0x001F)*2; + + // Extract date + $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980; + $v_month = ($p_header['mdate'] & 0x01E0) >> 5; + $v_day = $p_header['mdate'] & 0x001F; + + // Get UNIX date format + $p_header['mtime'] = mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year); + + } else { + $p_header['mtime'] = $GLOBALS['EXEC_TIME']; + } + + // Other informations + + // TBC + //for(reset($v_data); $key = key($v_data); next($v_data)) { + //} + + // Set the stored filename + $p_header['stored_filename'] = $p_header['filename']; + + // Set the status field + $p_header['status'] = "ok"; + + // Return + return $v_result; + } + + /** + * em_unzip::_readCentralFileHeader() + * + * { Description } + * + */ + function _readCentralFileHeader(&$p_header) { + $v_result=1; + + // Read the 4 bytes signature + $v_binary_data = @fread($this->_zip_fd, 4); + $v_data = unpack('Vid', $v_binary_data); + + // Check signature + if ($v_data['id'] != 0x02014b50) { + + // Error log + $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, 'Invalid archive structure'); + + // Return + return em_unzip::errorCode(); + } + + // Read the first 42 bytes of the header + $v_binary_data = fread($this->_zip_fd, 42); + + // Look for invalid block size + if (strlen($v_binary_data) != 42) { + $p_header['filename'] = ""; + $p_header['status'] = "invalid_header"; + + // Error log + $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, "Invalid block size : ".strlen($v_binary_data)); + + // Return + return em_unzip::errorCode(); + } + + // Extract the values + $p_header = unpack('vversion/vversion_extracted/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset', $v_binary_data); + + // Get filename + if ($p_header['filename_len'] != 0) + $p_header['filename'] = fread($this->_zip_fd, $p_header['filename_len']); + else + $p_header['filename'] = ''; + + // Get extra + if ($p_header['extra_len'] != 0) + $p_header['extra'] = fread($this->_zip_fd, $p_header['extra_len']); + else + $p_header['extra'] = ''; + + // Get comment + if ($p_header['comment_len'] != 0) + $p_header['comment'] = fread($this->_zip_fd, $p_header['comment_len']); + else + $p_header['comment'] = ''; + + // Extract properties + + // Recuperate date in UNIX format + if ($p_header['mdate'] && $p_header['mtime']) { + // Extract time + $v_hour = ($p_header['mtime'] & 0xF800) >> 11; + $v_minute = ($p_header['mtime'] & 0x07E0) >> 5; + $v_seconde = ($p_header['mtime'] & 0x001F)*2; + + // Extract date + $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980; + $v_month = ($p_header['mdate'] & 0x01E0) >> 5; + $v_day = $p_header['mdate'] & 0x001F; + + // Get UNIX date format + $p_header['mtime'] = mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year); + + } else { + $p_header['mtime'] = $GLOBALS['EXEC_TIME']; + } + + // Set the stored filename + $p_header['stored_filename'] = $p_header['filename']; + + // Set default status to ok + $p_header['status'] = 'ok'; + + // Look if it is a directory + if (substr($p_header['filename'], -1) == '/') { + $p_header['external'] = 0x41FF0010; + } + + + // Return + return $v_result; + } + + /** + * em_unzip::_readEndCentralDir() + * + * { Description } + * + */ + function _readEndCentralDir(&$p_central_dir) { + $v_result=1; + + // Go to the end of the zip file + $v_size = filesize($this->_zipname); + @fseek($this->_zip_fd, $v_size); + if (@ftell($this->_zip_fd) != $v_size) { + $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, + 'Unable to go to the end of the archive \'' + .$this->_zipname.'\''); + return em_unzip::errorCode(); + } + + // First try : look if this is an archive with no commentaries + // (most of the time) + // in this case the end of central dir is at 22 bytes of the file end + $v_found = 0; + if ($v_size > 26) { + @fseek($this->_zip_fd, $v_size-22); + if (($v_pos = @ftell($this->_zip_fd)) != ($v_size-22)) { + $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, + 'Unable to seek back to the middle of the archive \'' + .$this->_zipname.'\''); + return em_unzip::errorCode(); + } + + // Read for bytes + $v_binary_data = @fread($this->_zip_fd, 4); + $v_data = unpack('Vid', $v_binary_data); + + // Check signature + if ($v_data['id'] == 0x06054b50) { + $v_found = 1; + } + + $v_pos = ftell($this->_zip_fd); + } + + // Go back to the maximum possible size of the Central Dir End Record + if (!$v_found) { + $v_maximum_size = 65557; // 0xFFFF + 22; + if ($v_maximum_size > $v_size) + $v_maximum_size = $v_size; + @fseek($this->_zip_fd, $v_size-$v_maximum_size); + if (@ftell($this->_zip_fd) != ($v_size-$v_maximum_size)) { + $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, + 'Unable to seek back to the middle of the archive \'' + .$this->_zipname.'\''); + return em_unzip::errorCode(); + } + + // Read byte per byte in order to find the signature + $v_pos = ftell($this->_zip_fd); + $v_bytes = 0x00000000; + while ($v_pos < $v_size) { + // Read a byte + $v_byte = @fread($this->_zip_fd, 1); + + // Add the byte + $v_bytes = ($v_bytes << 8) | Ord($v_byte); + + // Compare the bytes + if ($v_bytes == 0x504b0506) { + $v_pos++; + break; + } + + $v_pos++; + } + + // Look if not found end of central dir + if ($v_pos == $v_size) { + $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, + "Unable to find End of Central Dir Record signature"); + return em_unzip::errorCode(); + } + } + + // Read the first 18 bytes of the header + $v_binary_data = fread($this->_zip_fd, 18); + + // Look for invalid block size + if (strlen($v_binary_data) != 18) { + $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, + "Invalid End of Central Dir Record size : " + .strlen($v_binary_data)); + return em_unzip::errorCode(); + } + + // Extract the values + $v_data = unpack('vdisk/vdisk_start/vdisk_entries/ventries/Vsize/Voffset/vcomment_size', $v_binary_data); + + // Check the global size + if (($v_pos + $v_data['comment_size'] + 18) != $v_size) { + $this->_errorLog(ARCHIVE_ZIP_ERR_BAD_FORMAT, + "Fail to find the right signature"); + return em_unzip::errorCode(); + } + + // Get comment + if ($v_data['comment_size'] != 0) + $p_central_dir['comment'] = fread($this->_zip_fd, $v_data['comment_size']); + else + $p_central_dir['comment'] = ''; + + $p_central_dir['entries'] = $v_data['entries']; + $p_central_dir['disk_entries'] = $v_data['disk_entries']; + $p_central_dir['offset'] = $v_data['offset']; + $p_central_dir['size'] = $v_data['size']; + $p_central_dir['disk'] = $v_data['disk']; + $p_central_dir['disk_start'] = $v_data['disk_start']; + + // Return + return $v_result; + } + + /** + * em_unzip::_dirCheck() + * + * { Description } + * + * @param [type] $p_is_dir + */ + function _dirCheck($p_dir, $p_is_dir=false) { + $v_result = 1; + + // Remove the final '/' + if (($p_is_dir) && (substr($p_dir, -1)=='/')) { + $p_dir = substr($p_dir, 0, strlen($p_dir)-1); + } + + // Check the directory availability + if ((is_dir($p_dir)) || ($p_dir == "")) { + return 1; + } + + // Extract parent directory + $p_parent_dir = dirname($p_dir); + + // Just a check + if ($p_parent_dir != $p_dir) { + // Look for parent directory + if ($p_parent_dir != "") { + if (($v_result = $this->_dirCheck($p_parent_dir)) != 1) { + return $v_result; + } + } + } + + // Create the directory + if (!@mkdir($p_dir, 0777)) { + $this->_errorLog(ARCHIVE_ZIP_ERR_DIR_CREATE_FAIL, + "Unable to create directory '$p_dir'"); + return em_unzip::errorCode(); + } + + // Return + return $v_result; + } + + /** + * em_unzip::_check_parameters() + * + * { Description } + * + * @param integer $p_error_code + * @param string $p_error_string + */ + function _check_parameters(&$p_params, $p_default) { + + // Check that param is an array + if (!is_array($p_params)) { + $this->_errorLog(ARCHIVE_ZIP_ERR_INVALID_PARAMETER, + 'Unsupported parameter, waiting for an array'); + return em_unzip::errorCode(); + } + + // Check that all the params are valid + for (reset($p_params); list($v_key, $v_value) = each($p_params); ) { + if (!isset($p_default[$v_key])) { + $this->_errorLog(ARCHIVE_ZIP_ERR_INVALID_PARAMETER, + 'Unsupported parameter with key \''.$v_key.'\''); + + return em_unzip::errorCode(); + } + } + + // Set the default values + for (reset($p_default); list($v_key, $v_value) = each($p_default); ) { + if (!isset($p_params[$v_key])) { + $p_params[$v_key] = $p_default[$v_key]; + } + } + + // Check specific parameters + $v_callback_list = array ('callback_pre_add','callback_post_add', + 'callback_pre_extract','callback_post_extract'); + for ($i=0; $i_errorLog(ARCHIVE_ZIP_ERR_INVALID_PARAM_VALUE, + "Callback '".$p_params[$v_key] + ."()' is not an existing function for " + ."parameter '".$v_key."'"); + return em_unzip::errorCode(); + } + } + } + + return(1); + } + + /** + * em_unzip::_errorLog() + * + * { Description } + * + * @param integer $p_error_code + * @param string $p_error_string + */ + function _errorLog($p_error_code=0, $p_error_string='') { + $this->_error_code = $p_error_code; + $this->_error_string = $p_error_string; + } + + /** + * em_unzip::_errorReset() + * + * { Description } + * + */ + function _errorReset() { + $this->_error_code = 1; + $this->_error_string = ''; + } + + /** + * _tool_PathReduction() + * + * { Description } + * + */ + function _tool_PathReduction($p_dir) { + $v_result = ""; + + // Look for not empty path + if ($p_dir != "") { + // Explode path by directory names + $v_list = explode("/", $p_dir); + + // Study directories from last to first + for ($i=sizeof($v_list)-1; $i>=0; $i--) { + // Look for current path + if ($v_list[$i] == ".") { + // Ignore this directory + // Should be the first $i=0, but no check is done + } else if ($v_list[$i] == "..") { + // Ignore it and ignore the $i-1 + $i--; + } else if (($v_list[$i] == "") && ($i!=(sizeof($v_list)-1)) && ($i!=0)) { + // Ignore only the double '//' in path, + // but not the first and last '/' + } else { + $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?"/".$v_result:""); + } + } + } + + // Return + return $v_result; + } + + /** + * _tool_PathInclusion() + * + * { Description } + * + */ + function _tool_PathInclusion($p_dir, $p_path) { + $v_result = 1; + + // Explode dir and path by directory separator + $v_list_dir = explode("/", $p_dir); + $v_list_dir_size = sizeof($v_list_dir); + $v_list_path = explode("/", $p_path); + $v_list_path_size = sizeof($v_list_path); + + // Study directories paths + $i = 0; + $j = 0; + while (($i < $v_list_dir_size) && ($j < $v_list_path_size) && ($v_result)) { + // Look for empty dir (path reduction) + if ($v_list_dir[$i] == '') { + $i++; + continue; + } + if ($v_list_path[$j] == '') { + $j++; + continue; + } + + // Compare the items + if ( ($v_list_dir[$i] != $v_list_path[$j]) && ($v_list_dir[$i] != '') && ( $v_list_path[$j] != '')) { + $v_result = 0; + } + + // Next items + $i++; + $j++; + } + + // Look if everything seems to be the same + if ($v_result) { + // Skip all the empty items + while (($j < $v_list_path_size) && ($v_list_path[$j] == '')) $j++; + while (($i < $v_list_dir_size) && ($v_list_dir[$i] == '')) $i++; + + if (($i >= $v_list_dir_size) && ($j >= $v_list_path_size)) { + // There are exactly the same + $v_result = 2; + } else if ($i < $v_list_dir_size) { + // The path is shorter than the dir + $v_result = 0; + } + } + + // Return + return $v_result; + } + + /** + * _tool_CopyBlock() + * + * { Description } + * + * @param integer $p_mode + */ + function _tool_CopyBlock($p_src, $p_dest, $p_size, $p_mode=0) { + $v_result = 1; + + if ($p_mode==0) { + while ($p_size != 0) { + $v_read_size = ($p_size < ARCHIVE_ZIP_READ_BLOCK_SIZE + ? $p_size : ARCHIVE_ZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($p_src, $v_read_size); + @fwrite($p_dest, $v_buffer, $v_read_size); + $p_size -= $v_read_size; + } + } else if ($p_mode==1) { + while ($p_size != 0) { + $v_read_size = ($p_size < ARCHIVE_ZIP_READ_BLOCK_SIZE + ? $p_size : ARCHIVE_ZIP_READ_BLOCK_SIZE); + $v_buffer = @gzread($p_src, $v_read_size); + @fwrite($p_dest, $v_buffer, $v_read_size); + $p_size -= $v_read_size; + } + } else if ($p_mode==2) { + while ($p_size != 0) { + $v_read_size = ($p_size < ARCHIVE_ZIP_READ_BLOCK_SIZE + ? $p_size : ARCHIVE_ZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($p_src, $v_read_size); + @gzwrite($p_dest, $v_buffer, $v_read_size); + $p_size -= $v_read_size; + } + } + else if ($p_mode==3) { + while ($p_size != 0) { + $v_read_size = ($p_size < ARCHIVE_ZIP_READ_BLOCK_SIZE + ? $p_size : ARCHIVE_ZIP_READ_BLOCK_SIZE); + $v_buffer = @gzread($p_src, $v_read_size); + @gzwrite($p_dest, $v_buffer, $v_read_size); + $p_size -= $v_read_size; + } + } + + // Return + return $v_result; + } + + /** + * _tool_Rename() + * + * { Description } + * + */ + function _tool_Rename($p_src, $p_dest) { + $v_result = 1; + + // Try to rename the files + if (!@rename($p_src, $p_dest)) { + + // Try to copy & unlink the src + if (!@copy($p_src, $p_dest)) { + $v_result = 0; + } else if (!@unlink($p_src)) { + $v_result = 0; + } + } + + // Return + return $v_result; + } + + /** + * _tool_TranslateWinPath() + * + * { Description } + * + * @param [type] $p_remove_disk_letter + */ + function _tool_TranslateWinPath($p_path, $p_remove_disk_letter=true) { + if (stristr(php_uname(), 'windows')) { + // Look for potential disk letter + if ( ($p_remove_disk_letter) + && (($v_position = strpos($p_path, ':')) != false)) { + $p_path = substr($p_path, $v_position+1); + } + // Change potential windows directory separator + if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) { + $p_path = strtr($p_path, '\\', '/'); + } + } + return $p_path; + } + +} + +?> \ No newline at end of file Index: typo3/sysext/em/mod1/class.em_xmlhandler.php =================================================================== --- typo3/sysext/em/mod1/class.em_xmlhandler.php (revision 0) +++ typo3/sysext/em/mod1/class.em_xmlhandler.php (revision 0) @@ -0,0 +1,602 @@ + +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* A copy is found in the textfile GPL.txt and important notices to the license +* from the author is found in LICENSE.txt distributed with these scripts. +* +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ + +/** + * XML handling class for the TYPO3 Extension Manager. + * + * It contains methods for handling the XML files involved with the EM, + * such as the list of extension mirrors and the list of available extensions. + * + * @author Karsten Dambekalns + * @package TYPO3 + * @subpackage EM + */ +class SC_mod_tools_em_xmlhandler { + + /** + * Enxtension Manager module + * + * @var SC_mod_tools_em_index + */ + var $emObj; + + /** + * Holds the parsed XML from extensions.xml.gz + * @see parseExtensionsXML() + * + * @var array + */ + var $extXMLResult = array(); + var $extensionsXML = array(); + var $reviewStates = null; + var $useObsolete = false; + + /** + * Reduces the entries in $this->extensionsXML to the latest version per extension and removes entries not matching the search parameter + * + * @param string $search The list of extensions is reduced to entries matching this. If empty, the full list is returned. + * @param string $owner If set only extensions of that user are fetched + * @param string $order A field to order the result by + * @param boolean $allExt If set also unreviewed and obsolete extensions are shown + * @param boolean $allVer If set returns all version of an extension, otherwise only the last + * @param integer $offset Offset to return result from (goes into LIMIT clause) + * @param integer $limit Maximum number of entries to return (goes into LIMIT clause) + * @param boolean $exactMatch If set search is done for exact matches of extension keys only + * @return void + */ + function searchExtensionsXML($search, $owner='', $order='', $allExt=false, $allVer=false, $offset=0, $limit=500, $exactMatch=false) { + $where = '1=1'; + if ($search && $exactMatch) { + $where.= ' AND extkey=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($search, 'cache_extensions'); + } elseif ($search) { + $quotedSearch = $GLOBALS['TYPO3_DB']->quoteStr( + $GLOBALS['TYPO3_DB']->escapeStrForLike($search, 'cache_extensions'), + 'cache_extensions' + ); + $where .= ' AND (extkey LIKE \'%' . $quotedSearch . '%\' OR title LIKE \'%' . $quotedSearch . '%\')'; + + } + if ($owner) { + $where.= ' AND ownerusername='.$GLOBALS['TYPO3_DB']->fullQuoteStr($owner, 'cache_extensions'); + } + + // Show extensions without a review or that have passed a review, but not insecure extensions + $where .= ' AND reviewstate >= 0'; + + if (!$this->useObsolete) { + // 5 == obsolete + $where.= ' AND state != 5'; + } + switch ($order) { + case 'author_company': + $forder = 'authorname, authorcompany'; + break; + case 'state': + $forder = 'state'; + break; + case 'cat': + default: + $forder = 'category'; + break; + } + $order = $forder.', title'; + if (!$allVer) { + $where .= ' AND lastversion > 0'; + } + $this->catArr = array(); + $idx = 0; + foreach ($this->emObj->defaultCategories['cat'] as $catKey => $tmp) { + $this->catArr[$idx] = $catKey; + $idx++; + } + $this->stateArr = array(); + $idx = 0; + foreach ($this->emObj->states as $state => $tmp) { + $this->stateArr[$idx] = $state; + $idx++; + } + + // Fetch count + $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('*', 'cache_extensions', $where); + $this->matchingCount = $count; + + $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'cache_extensions', $where, '', $order, $offset.','.$limit); + $this->extensionsXML = array(); + while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) { + $row['category'] = $this->catArr[$row['category']]; + $row['state'] = $this->stateArr[$row['state']]; + + if (!is_array($this->extensionsXML[$row['extkey']])) { + $this->extensionsXML[$row['extkey']] = array(); + $this->extensionsXML[$row['extkey']]['downloadcounter'] = $row['alldownloadcounter']; + } + if (!is_array($this->extensionsXML[$row['extkey']]['versions'])) { + $this->extensionsXML[$row['extkey']]['versions'] = array(); + } + $row['dependencies'] = unserialize($row['dependencies']); + $this->extensionsXML[$row['extkey']]['versions'][$row['version']] = $row; + } + $GLOBALS['TYPO3_DB']->sql_free_result($res); + } + + /** + * Reduces the entries in $this->extensionsXML to the latest version per extension and removes entries not matching the search parameter + * The extension key has to be a valid one as search is done for exact matches only. + * + * @param string $search The list of extensions is reduced to entries with exactely this extension key. If empty, the full list is returned. + * @param string $owner If set only extensions of that user are fetched + * @param string $order A field to order the result by + * @param boolean $allExt If set also unreviewed and obsolete extensions are shown + * @param boolean $allVer If set returns all version of an extension, otherwise only the last + * @param integer $offset Offset to return result from (goes into LIMIT clause) + * @param integer $limit Maximum number of entries to return (goes into LIMIT clause) + * @return void + */ + function searchExtensionsXMLExact($search, $owner='', $order='', $allExt=false, $allVer=false, $offset=0, $limit=500) { + $this->searchExtensionsXML($search, $owner, $order, $allExt, $allVer, $offset, $limit, true); + } + + function countExtensions() { + $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('extkey', 'cache_extensions', '1=1', 'extkey'); + $cnt = $GLOBALS['TYPO3_DB']->sql_num_rows($res); + $GLOBALS['TYPO3_DB']->sql_free_result($res); + return $cnt; + } + + /** + * Loads the pre-parsed extension list + * + * @return boolean true on success, false on error + */ + function loadExtensionsXML() { + $this->searchExtensionsXML('', '', '', true); + } + + /** + * Frees the pre-parsed extension list + * + * @return void + */ + function freeExtensionsXML() { + unset($this->extensionsXML); + $this->extensionsXML = array(); + } + + /** + * Removes all extension with a certain state from the list + * + * @param array &$extensions The "versions" subpart of the extension list + * @return void + */ + function removeObsolete(&$extensions) { + if($this->useObsolete) return; + + foreach ($extensions as $version => $data) { + if($data['state']=='obsolete') + unset($extensions[$version]); + } + } + + /** + * Returns the reviewstate of a specific extension-key/version + * + * @param string $extKey + * @param string $version: ... + * @return integer Review state, if none is set 0 is returned as default. + */ + function getReviewState($extKey, $version) { + $where = 'extkey='.$GLOBALS['TYPO3_DB']->fullQuoteStr($extKey, 'cache_extensions').' AND version='.$GLOBALS['TYPO3_DB']->fullQuoteStr($version, 'cache_extensions'); + $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('reviewstate', 'cache_extensions', $where); + if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) { + return $row['reviewstate']; + } + $GLOBALS['TYPO3_DB']->sql_free_result($res); + return 0; + } + + /** + * ***************PARSING METHODS*********************** + */ + /** + * Enter description here... + * + * @param unknown_type $parser + * @param unknown_type $name + * @param unknown_type $attrs + * @return [type] ... + */ + function startElement($parser, $name, $attrs) { + switch($name) { + case 'extensions': + break; + case 'extension': + $this->currentExt = $attrs['extensionkey']; + break; + case 'version': + $this->currentVersion = $attrs['version']; + $this->extXMLResult[$this->currentExt]['versions'][$this->currentVersion] = array(); + break; + default: + $this->currentTag = $name; + } + } + + /** + * Enter description here... + * + * @param unknown_type $parser + * @param unknown_type $name + * @return [type] ... + */ + function endElement($parser, $name) { + switch($name) { + case 'extension': + unset($this->currentExt); + break; + case 'version': + unset($this->currentVersion); + break; + default: + unset($this->currentTag); + } + } + + /** + * Enter description here... + * + * @param unknown_type $parser + * @param unknown_type $data + * @return [type] ... + */ + function characterData($parser, $data) { + if(isset($this->currentTag)) { + if(!isset($this->currentVersion) && $this->currentTag == 'downloadcounter') { + $this->extXMLResult[$this->currentExt]['downloadcounter'] = trim($data); + } elseif($this->currentTag == 'dependencies') { + $data = @unserialize($data); + if(is_array($data)) { + $dep = array(); + foreach($data as $v) { + $dep[$v['kind']][$v['extensionKey']] = $v['versionRange']; + } + $this->extXMLResult[$this->currentExt]['versions'][$this->currentVersion]['dependencies'] = $dep; + } + } elseif($this->currentTag == 'reviewstate') { + $this->reviewStates[$this->currentExt][$this->currentVersion] = (int)trim($data); + $this->extXMLResult[$this->currentExt]['versions'][$this->currentVersion]['reviewstate'] = (int)trim($data); + } else { + $this->extXMLResult[$this->currentExt]['versions'][$this->currentVersion][$this->currentTag] .= trim($data); + } + } + } + + /** + * Parses content of mirrors.xml into a suitable array + * + * @param string XML data file to parse + * @return string HTLML output informing about result + */ + function parseExtensionsXML($filename) { + + $parser = xml_parser_create(); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); + xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0); + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'utf-8'); + xml_set_element_handler($parser, array(&$this,'startElement'), array(&$this,'endElement')); + xml_set_character_data_handler($parser, array(&$this,'characterData')); + + $fp = gzopen($filename, 'rb'); + if (!$fp) { + $content.= 'Error opening XML extension file "'.$filename.'"'; + return $content; + } + $string = gzread($fp, 0xffff); // Read 64KB + + $this->revCatArr = array(); + $idx = 0; + foreach ($this->emObj->defaultCategories['cat'] as $catKey => $tmp) { + $this->revCatArr[$catKey] = $idx++; + } + + $this->revStateArr = array(); + $idx = 0; + foreach ($this->emObj->states as $state => $tmp) { + $this->revStateArr[$state] = $idx++; + } + + $GLOBALS['TYPO3_DB']->exec_TRUNCATEquery('cache_extensions'); + + $extcount = 0; + @ini_set('pcre.backtrack_limit', 500000); + do { + if (preg_match('/.*(.*<\/extension>)/suU', $string, $match)) { + // Parse content: + if (!xml_parse($parser, $match[0], 0)) { + $content.= 'Error in XML parser while decoding extensions XML file. Line '.xml_get_current_line_number($parser).': '.xml_error_string(xml_get_error_code($parser)); + $error = true; + break; + } + $this->storeXMLResult(); + $this->extXMLResult = array(); + $extcount++; + $string = substr($string, strlen($match[0])); + } elseif(function_exists('preg_last_error') && preg_last_error()) { + $errorcodes = array( + 0 => 'PREG_NO_ERROR', + 1 => 'PREG_INTERNAL_ERROR', + 2 => 'PREG_BACKTRACK_LIMIT_ERROR', + 3 => 'PREG_RECURSION_LIMIT_ERROR', + 4 => 'PREG_BAD_UTF8_ERROR' + ); + $content.= 'Error in regular expression matching, code: '.$errorcodes[preg_last_error()].'
See http://www.php.net/manual/en/function.preg-last-error.php'; + $error = true; + break; + } else { + if(gzeof($fp)) break; // Nothing more can be read + $string .= gzread($fp, 0xffff); // Read another 64KB + } + } while (true); + + xml_parser_free($parser); + gzclose($fp); + + if(!$error) { + $flashMessage = t3lib_div::makeInstance( + 't3lib_FlashMessage', + sprintf($GLOBALS['LANG']->getLL('ext_import_extlist_updated'), $extcount), + $GLOBALS['LANG']->getLL('ext_import_extlist_updated_header') + ); + $content .= $flashMessage->render(); + } + + return $content; + } + + function storeXMLResult() { + foreach ($this->extXMLResult as $extkey => $extArr) { + $max = -1; + $maxrev = -1; + $last = ''; + $lastrev = ''; + $usecat = ''; + $usetitle = ''; + $usestate = ''; + $useauthorcompany = ''; + $useauthorname = ''; + $verArr = array(); + foreach ($extArr['versions'] as $version => $vArr) { + $iv = $this->emObj->makeVersion($version, 'int'); + if ($vArr['title']&&!$usetitle) { + $usetitle = $vArr['title']; + } + if ($vArr['state']&&!$usestate) { + $usestate = $vArr['state']; + } + if ($vArr['authorcompany']&&!$useauthorcompany) { + $useauthorcompany = $vArr['authorcompany']; + } + if ($vArr['authorname']&&!$useauthorname) { + $useauthorname = $vArr['authorname']; + } + $verArr[$version] = $iv; + if ($iv>$max) { + $max = $iv; + $last = $version; + if ($vArr['title']) { + $usetitle = $vArr['title']; + } + if ($vArr['state']) { + $usestate = $vArr['state']; + } + if ($vArr['authorcompany']) { + $useauthorcompany = $vArr['authorcompany']; + } + if ($vArr['authorname']) { + $useauthorname = $vArr['authorname']; + } + $usecat = $vArr['category']; + } + if ($vArr['reviewstate'] && ($iv>$maxrev)) { + $maxrev = $iv; + $lastrev = $version; + } + } + if (!strlen($usecat)) { + $usecat = 4; // Extensions without a category end up in "misc" + } else { + if (isset($this->revCatArr[$usecat])) { + $usecat = $this->revCatArr[$usecat]; + } else { + $usecat = 4; // Extensions without a category end up in "misc" + } + } + if (isset($this->revStateArr[$usestate])) { + $usestate = $this->revCatArr[$usestate]; + } else { + $usestate = 999; // Extensions without a category end up in "misc" + } + foreach ($extArr['versions'] as $version => $vArr) { + $vArr['version'] = $version; + $vArr['intversion'] = $verArr[$version]; + $vArr['extkey'] = $extkey; + $vArr['alldownloadcounter'] = $extArr['downloadcounter']; + $vArr['dependencies'] = serialize($vArr['dependencies']); + $vArr['category'] = $usecat; + $vArr['title'] = $usetitle; + if ($version==$last) { + $vArr['lastversion'] = 1; + } + if ($version==$lastrev) { + $vArr['lastreviewedversion'] = 1; + } + $vArr['state'] = isset($this->revStateArr[$vArr['state']])?$this->revStateArr[$vArr['state']]:$usestate; // 999 = not set category + $GLOBALS['TYPO3_DB']->exec_INSERTquery('cache_extensions', $vArr); + } + } + } + + /** + * Parses content of mirrors.xml into a suitable array + * + * @param string $string: XML data to parse + * @return string HTLML output informing about result + */ + function parseMirrorsXML($string) { + global $TYPO3_CONF_VARS; + + // Create parser: + $parser = xml_parser_create(); + $vals = array(); + $index = array(); + + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); + xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0); + + $preg_result = array(); + preg_match('/^[[:space:]]*<\?xml[^>]*encoding[[:space:]]*=[[:space:]]*"([^"]*)"/',substr($string,0,200),$preg_result); + $theCharset = $preg_result[1] ? $preg_result[1] : ($TYPO3_CONF_VARS['BE']['forceCharset'] ? $TYPO3_CONF_VARS['BE']['forceCharset'] : 'iso-8859-1'); + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $theCharset); // us-ascii / utf-8 / iso-8859-1 + + // Parse content: + xml_parse_into_struct($parser, $string, $vals, $index); + + // If error, return error message: + if (xml_get_error_code($parser)) { + $line = xml_get_current_line_number($parser); + $error = xml_error_string(xml_get_error_code($parser)); + xml_parser_free($parser); + return 'Error in XML parser while decoding mirrors XML file. Line '.$line.': '.$error; + } else { + // Init vars: + $stack = array(array()); + $stacktop = 0; + $mirrornumber = 0; + $current=array(); + $tagName = ''; + $documentTag = ''; + + // Traverse the parsed XML structure: + foreach($vals as $val) { + + // First, process the tag-name (which is used in both cases, whether "complete" or "close") + $tagName = ($val['tag']=='mirror' && $val['type']=='open') ? '__plh' : $val['tag']; + if (!$documentTag) $documentTag = $tagName; + + // Setting tag-values, manage stack: + switch($val['type']) { + case 'open': // If open tag it means there is an array stored in sub-elements. Therefore increase the stackpointer and reset the accumulation array: + $current[$tagName] = array(); // Setting blank place holder + $stack[$stacktop++] = $current; + $current = array(); + break; + case 'close': // If the tag is "close" then it is an array which is closing and we decrease the stack pointer. + $oldCurrent = $current; + $current = $stack[--$stacktop]; + end($current); // Going to the end of array to get placeholder key, key($current), and fill in array next: + if($tagName=='mirror') { + unset($current['__plh']); + $current[$oldCurrent['host']] = $oldCurrent; + } else { + $current[key($current)] = $oldCurrent; + } + unset($oldCurrent); + break; + case 'complete': // If "complete", then it's a value. If the attribute "base64" is set, then decode the value, otherwise just set it. + $current[$tagName] = (string)$val['value']; // Had to cast it as a string - otherwise it would be evaluate false if tested with isset()!! + break; + } + } + return $current[$tagName]; + } + } + + /** + * Parses content of *-l10n.xml into a suitable array + * + * @param string $string: XML data to parse + * @return array Array representation of XML data + */ + function parseL10nXML($string) { + // Create parser: + $parser = xml_parser_create(); + $vals = array(); + $index = array(); + + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); + xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0); + + // Parse content: + xml_parse_into_struct($parser, $string, $vals, $index); + + // If error, return error message: + if (xml_get_error_code($parser)) { + $line = xml_get_current_line_number($parser); + $error = xml_error_string(xml_get_error_code($parser)); + debug($error); + xml_parser_free($parser); + return 'Error in XML parser while decoding l10n XML file. Line '.$line.': '.$error; + } else { + // Init vars: + $stack = array(array()); + $stacktop = 0; + $mirrornumber = 0; + $current=array(); + $tagName = ''; + $documentTag = ''; + + // Traverse the parsed XML structure: + foreach($vals as $val) { + + // First, process the tag-name (which is used in both cases, whether "complete" or "close") + $tagName = ($val['tag']=='languagepack' && $val['type']=='open') ? $val['attributes']['language'] : $val['tag']; + if (!$documentTag) $documentTag = $tagName; + + // Setting tag-values, manage stack: + switch($val['type']) { + case 'open': // If open tag it means there is an array stored in sub-elements. Therefore increase the stackpointer and reset the accumulation array: + $current[$tagName] = array(); // Setting blank place holder + $stack[$stacktop++] = $current; + $current = array(); + break; + case 'close': // If the tag is "close" then it is an array which is closing and we decrease the stack pointer. + $oldCurrent = $current; + $current = $stack[--$stacktop]; + end($current); // Going to the end of array to get placeholder key, key($current), and fill in array next: + $current[key($current)] = $oldCurrent; + unset($oldCurrent); + break; + case 'complete': // If "complete", then it's a value. If the attribute "base64" is set, then decode the value, otherwise just set it. + $current[$tagName] = (string)$val['value']; // Had to cast it as a string - otherwise it would be evaluate false if tested with isset()!! + break; + } + } + return $current[$tagName]; + } + } +} + +?> \ No newline at end of file Index: typo3/sysext/em/mod1/class.nusoap.php =================================================================== --- typo3/sysext/em/mod1/class.nusoap.php (revision 0) +++ typo3/sysext/em/mod1/class.nusoap.php (revision 0) @@ -0,0 +1,6203 @@ +globalDebugLevel = 9; + +/** +* +* nusoap_base +* +* @author Dietrich Ayala +* @version $Id: class.nusoap.php 6339 2009-11-05 21:21:54Z masi $ +* @access public +*/ +class nusoap_base { + /** + * Identification for HTTP headers. + * + * @var string + * @access private + */ + var $title = 'NuSOAP'; + /** + * Version for HTTP headers. + * + * @var string + * @access private + */ + var $version = '0.7.2'; + /** + * CVS revision for HTTP headers. + * + * @var string + * @access private + */ + var $revision = '$Revision: 6339 $'; + /** + * Current error string (manipulated by getError/setError) + * + * @var string + * @access private + */ + var $error_str = ''; + /** + * Current debug string (manipulated by debug/appendDebug/clearDebug/getDebug/getDebugAsXMLComment) + * + * @var string + * @access private + */ + var $debug_str = ''; + /** + * toggles automatic encoding of special characters as entities + * (should always be true, I think) + * + * @var boolean + * @access private + */ + var $charencoding = true; + /** + * the debug level for this instance + * + * @var integer + * @access private + */ + var $debugLevel; + + /** + * set schema version + * + * @var string + * @access public + */ + var $XMLSchemaVersion = 'http://www.w3.org/2001/XMLSchema'; + + /** + * charset encoding for outgoing messages + * + * @var string + * @access public + */ + var $soap_defencoding = 'ISO-8859-1'; + //var $soap_defencoding = 'UTF-8'; + + /** + * namespaces in an array of prefix => uri + * + * this is "seeded" by a set of constants, but it may be altered by code + * + * @var array + * @access public + */ + var $namespaces = array( + 'SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/', + 'xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/' + ); + + /** + * namespaces used in the current context, e.g. during serialization + * + * @var array + * @access private + */ + var $usedNamespaces = array(); + + /** + * XML Schema types in an array of uri => (array of xml type => php type) + * is this legacy yet? + * no, this is used by the xmlschema class to verify type => namespace mappings. + * @var array + * @access public + */ + var $typemap = array( + 'http://www.w3.org/2001/XMLSchema' => array( + 'string'=>'string','boolean'=>'boolean','float'=>'double','double'=>'double','decimal'=>'double', + 'duration'=>'','dateTime'=>'string','time'=>'string','date'=>'string','gYearMonth'=>'', + 'gYear'=>'','gMonthDay'=>'','gDay'=>'','gMonth'=>'','hexBinary'=>'string','base64Binary'=>'string', + // abstract "any" types + 'anyType'=>'string','anySimpleType'=>'string', + // derived datatypes + 'normalizedString'=>'string','token'=>'string','language'=>'','NMTOKEN'=>'','NMTOKENS'=>'','Name'=>'','NCName'=>'','ID'=>'', + 'IDREF'=>'','IDREFS'=>'','ENTITY'=>'','ENTITIES'=>'','integer'=>'integer','nonPositiveInteger'=>'integer', + 'negativeInteger'=>'integer','long'=>'integer','int'=>'integer','short'=>'integer','byte'=>'integer','nonNegativeInteger'=>'integer', + 'unsignedLong'=>'','unsignedInt'=>'','unsignedShort'=>'','unsignedByte'=>'','positiveInteger'=>''), + 'http://www.w3.org/2000/10/XMLSchema' => array( + 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double', + 'float'=>'double','dateTime'=>'string', + 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'), + 'http://www.w3.org/1999/XMLSchema' => array( + 'i4'=>'','int'=>'integer','boolean'=>'boolean','string'=>'string','double'=>'double', + 'float'=>'double','dateTime'=>'string', + 'timeInstant'=>'string','base64Binary'=>'string','base64'=>'string','ur-type'=>'array'), + 'http://soapinterop.org/xsd' => array('SOAPStruct'=>'struct'), + 'http://schemas.xmlsoap.org/soap/encoding/' => array('base64'=>'string','array'=>'array','Array'=>'array'), + 'http://xml.apache.org/xml-soap' => array('Map') + ); + + /** + * XML entities to convert + * + * @var array + * @access public + * @deprecated since at least TYPO3 4.3, will be removed in TYPO3 4.5. + * @see expandEntities + */ + var $xmlEntities = array('quot' => '"','amp' => '&', + 'lt' => '<','gt' => '>','apos' => "'"); + + /** + * constructor + * + * @access public + */ + function nusoap_base() { + $this->debugLevel = $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel; + } + + /** + * gets the global debug level, which applies to future instances + * + * @return integer Debug level 0-9, where 0 turns off + * @access public + */ + function getGlobalDebugLevel() { + return $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel; + } + + /** + * sets the global debug level, which applies to future instances + * + * @param int $level Debug level 0-9, where 0 turns off + * @access public + */ + function setGlobalDebugLevel($level) { + $GLOBALS['_transient']['static']['nusoap_base']->globalDebugLevel = $level; + } + + /** + * gets the debug level for this instance + * + * @return int Debug level 0-9, where 0 turns off + * @access public + */ + function getDebugLevel() { + return $this->debugLevel; + } + + /** + * sets the debug level for this instance + * + * @param int $level Debug level 0-9, where 0 turns off + * @access public + */ + function setDebugLevel($level) { + $this->debugLevel = $level; + } + + /** + * adds debug data to the instance debug string with formatting + * + * @param string $string debug data + * @access private + */ + function debug($string){ + if ($this->debugLevel > 0) { + $this->appendDebug($this->getmicrotime().' '.get_class($this).": $string\n"); + } + } + + /** + * adds debug data to the instance debug string without formatting + * + * @param string $string debug data + * @access public + */ + function appendDebug($string){ + if ($this->debugLevel > 0) { + // it would be nice to use a memory stream here to use + // memory more efficiently + $this->debug_str .= $string; + } + } + + /** + * clears the current debug data for this instance + * + * @access public + */ + function clearDebug() { + // it would be nice to use a memory stream here to use + // memory more efficiently + $this->debug_str = ''; + } + + /** + * gets the current debug data for this instance + * + * @return debug data + * @access public + */ + function &getDebug() { + // it would be nice to use a memory stream here to use + // memory more efficiently + return $this->debug_str; + } + + /** + * gets the current debug data for this instance as an XML comment + * this may change the contents of the debug data + * + * @return debug data as an XML comment + * @access public + */ + function &getDebugAsXMLComment() { + // it would be nice to use a memory stream here to use + // memory more efficiently + while (strpos($this->debug_str, '--')) { + $this->debug_str = str_replace('--', '- -', $this->debug_str); + } + return ""; + } + + /** + * expands entities, e.g. changes '<' to '<'. + * + * @param string $val The string in which to expand entities. + * @access private + */ + function expandEntities($val) { + if ($this->charencoding) { + $val = str_replace('&', '&', $val); + $val = str_replace("'", ''', $val); + $val = str_replace('"', '"', $val); + $val = str_replace('<', '<', $val); + $val = str_replace('>', '>', $val); + } + return $val; + } + + /** + * returns error string if present + * + * @return mixed error string or false + * @access public + */ + function getError(){ + if($this->error_str != ''){ + return $this->error_str; + } + return false; + } + + /** + * sets error string + * + * @return boolean $string error string + * @access private + */ + function setError($str){ + $this->error_str = $str; + } + + /** + * detect if array is a simple array or a struct (associative array) + * + * @param mixed $val The PHP array + * @return string (arraySimple|arrayStruct) + * @access private + */ + function isArraySimpleOrStruct($val) { + $keyList = array_keys($val); + foreach ($keyList as $keyListValue) { + if (!is_int($keyListValue)) { + return 'arrayStruct'; + } + } + return 'arraySimple'; + } + + /** + * serializes PHP values in accordance w/ section 5. Type information is + * not serialized if $use == 'literal'. + * + * @param mixed $val The value to serialize + * @param string $name The name (local part) of the XML element + * @param string $type The XML schema type (local part) for the element + * @param string $name_ns The namespace for the name of the XML element + * @param string $type_ns The namespace for the type of the element + * @param array $attributes The attributes to serialize as name=>value pairs + * @param string $use The WSDL "use" (encoded|literal) + * @return string The serialized element, possibly with child elements + * @access public + */ + function serialize_val($val,$name=false,$type=false,$name_ns=false,$type_ns=false,$attributes=false,$use='encoded'){ + $this->debug("in serialize_val: name=$name, type=$type, name_ns=$name_ns, type_ns=$type_ns, use=$use"); + $this->appendDebug('value=' . $this->varDump($val)); + $this->appendDebug('attributes=' . $this->varDump($attributes)); + + if(is_object($val) && get_class($val) == 'soapval'){ + return $val->serialize($use); + } + // force valid name if necessary + if (is_numeric($name)) { + $name = '__numeric_' . $name; + } elseif (! $name) { + $name = 'noname'; + } + // if name has ns, add ns prefix to name + $xmlns = ''; + if($name_ns){ + $prefix = 'nu'.rand(1000,9999); + $name = $prefix.':'.$name; + $xmlns .= " xmlns:$prefix=\"$name_ns\""; + } + // if type is prefixed, create type prefix + if($type_ns != '' && $type_ns == $this->namespaces['xsd']){ + // need to fix this. shouldn't default to xsd if no ns specified + // w/o checking against typemap + $type_prefix = 'xsd'; + } elseif($type_ns){ + $type_prefix = 'ns'.rand(1000,9999); + $xmlns .= " xmlns:$type_prefix=\"$type_ns\""; + } + // serialize attributes if present + $atts = ''; + if($attributes){ + foreach($attributes as $k => $v){ + $atts .= " $k=\"".$this->expandEntities($v).'"'; + } + } + // serialize null value + if (is_null($val)) { + if ($use == 'literal') { + // TODO: depends on minOccurs + return "<$name$xmlns $atts/>"; + } else { + if (isset($type) && isset($type_prefix)) { + $type_str = " xsi:type=\"$type_prefix:$type\""; + } else { + $type_str = ''; + } + return "<$name$xmlns$type_str $atts xsi:nil=\"true\"/>"; + } + } + // serialize if an xsd built-in primitive type + if($type != '' && isset($this->typemap[$this->XMLSchemaVersion][$type])){ + if (is_bool($val)) { + if ($type == 'boolean') { + $val = $val ? 'true' : 'false'; + } elseif (! $val) { + $val = 0; + } + } else if (is_string($val)) { + $val = $this->expandEntities($val); + } + if ($use == 'literal') { + return "<$name$xmlns $atts>$val"; + } else { + return "<$name$xmlns $atts xsi:type=\"xsd:$type\">$val"; + } + } + // detect type and serialize + $xml = ''; + switch(true) { + case (is_bool($val) || $type == 'boolean'): + if ($type == 'boolean') { + $val = $val ? 'true' : 'false'; + } elseif (! $val) { + $val = 0; + } + if ($use == 'literal') { + $xml .= "<$name$xmlns $atts>$val"; + } else { + $xml .= "<$name$xmlns xsi:type=\"xsd:boolean\"$atts>$val"; + } + break; + case (is_int($val) || is_long($val) || $type == 'int'): + if ($use == 'literal') { + $xml .= "<$name$xmlns $atts>$val"; + } else { + $xml .= "<$name$xmlns xsi:type=\"xsd:int\"$atts>$val"; + } + break; + case (is_float($val)|| is_double($val) || $type == 'float'): + if ($use == 'literal') { + $xml .= "<$name$xmlns $atts>$val"; + } else { + $xml .= "<$name$xmlns xsi:type=\"xsd:float\"$atts>$val"; + } + break; + case (is_string($val) || $type == 'string'): + $val = $this->expandEntities($val); + if ($use == 'literal') { + $xml .= "<$name$xmlns $atts>$val"; + } else { + $xml .= "<$name$xmlns xsi:type=\"xsd:string\"$atts>$val"; + } + break; + case is_object($val): + if (! $name) { + $name = get_class($val); + $this->debug("In serialize_val, used class name $name as element name"); + } else { + $this->debug("In serialize_val, do not override name $name for element name for class " . get_class($val)); + } + foreach(get_object_vars($val) as $k => $v){ + $pXml = isset($pXml) ? $pXml.$this->serialize_val($v,$k,false,false,false,false,$use) : $this->serialize_val($v,$k,false,false,false,false,$use); + } + $xml .= '<'.$name.'>'.$pXml.''; + break; + break; + case (is_array($val) || $type): + // detect if struct or array + $valueType = $this->isArraySimpleOrStruct($val); + if($valueType=='arraySimple' || preg_match('/^ArrayOf/',$type)){ + $i = 0; + if(is_array($val) && count($val)> 0){ + foreach($val as $v){ + if(is_object($v) && get_class($v) == 'soapval'){ + $tt_ns = $v->type_ns; + $tt = $v->type; + } elseif (is_array($v)) { + $tt = $this->isArraySimpleOrStruct($v); + } else { + $tt = gettype($v); + } + $array_types[$tt] = 1; + // TODO: for literal, the name should be $name + $xml .= $this->serialize_val($v,'item',false,false,false,false,$use); + ++$i; + } + if(count($array_types) > 1){ + $array_typename = 'xsd:anyType'; + } elseif(isset($tt) && isset($this->typemap[$this->XMLSchemaVersion][$tt])) { + if ($tt == 'integer') { + $tt = 'int'; + } + $array_typename = 'xsd:'.$tt; + } elseif(isset($tt) && $tt == 'arraySimple'){ + $array_typename = 'SOAP-ENC:Array'; + } elseif(isset($tt) && $tt == 'arrayStruct'){ + $array_typename = 'unnamed_struct_use_soapval'; + } else { + // if type is prefixed, create type prefix + if ($tt_ns != '' && $tt_ns == $this->namespaces['xsd']){ + $array_typename = 'xsd:' . $tt; + } elseif ($tt_ns) { + $tt_prefix = 'ns' . rand(1000, 9999); + $array_typename = "$tt_prefix:$tt"; + $xmlns .= " xmlns:$tt_prefix=\"$tt_ns\""; + } else { + $array_typename = $tt; + } + } + $array_type = $i; + if ($use == 'literal') { + $type_str = ''; + } else if (isset($type) && isset($type_prefix)) { + $type_str = " xsi:type=\"$type_prefix:$type\""; + } else { + $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"".$array_typename."[$array_type]\""; + } + // empty array + } else { + if ($use == 'literal') { + $type_str = ''; + } else if (isset($type) && isset($type_prefix)) { + $type_str = " xsi:type=\"$type_prefix:$type\""; + } else { + $type_str = " xsi:type=\"SOAP-ENC:Array\" SOAP-ENC:arrayType=\"xsd:anyType[0]\""; + } + } + // TODO: for array in literal, there is no wrapper here + $xml = "<$name$xmlns$type_str$atts>".$xml.""; + } else { + // got a struct + if(isset($type) && isset($type_prefix)){ + $type_str = " xsi:type=\"$type_prefix:$type\""; + } else { + $type_str = ''; + } + if ($use == 'literal') { + $xml .= "<$name$xmlns $atts>"; + } else { + $xml .= "<$name$xmlns$type_str$atts>"; + } + foreach($val as $k => $v){ + // Apache Map + if ($type == 'Map' && $type_ns == 'http://xml.apache.org/xml-soap') { + $xml .= ''; + $xml .= $this->serialize_val($k,'key',false,false,false,false,$use); + $xml .= $this->serialize_val($v,'value',false,false,false,false,$use); + $xml .= ''; + } else { + $xml .= $this->serialize_val($v,$k,false,false,false,false,$use); + } + } + $xml .= ""; + } + break; + default: + $xml .= 'not detected, got '.gettype($val).' for '.$val; + break; + } + return $xml; + } + + /** + * serializes a message + * + * @param string $body the XML of the SOAP body + * @param mixed $headers optional string of XML with SOAP header content, or array of soapval objects for SOAP headers + * @param array $namespaces optional the namespaces used in generating the body and headers + * @param string $style optional (rpc|document) + * @param string $use optional (encoded|literal) + * @param string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded) + * @return string the message + * @access public + */ + function serializeEnvelope($body,$headers=false,$namespaces=array(),$style='rpc',$use='encoded',$encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'){ + // TODO: add an option to automatically run utf8_encode on $body and $headers + // if $this->soap_defencoding is UTF-8. Not doing this automatically allows + // one to send arbitrary UTF-8 characters, not just characters that map to ISO-8859-1 + + $this->debug("In serializeEnvelope length=" . strlen($body) . " body (max 1000 characters)=" . substr($body, 0, 1000) . " style=$style use=$use encodingStyle=$encodingStyle"); + $this->debug("headers:"); + $this->appendDebug($this->varDump($headers)); + $this->debug("namespaces:"); + $this->appendDebug($this->varDump($namespaces)); + + // serialize namespaces + $ns_string = ''; + foreach(array_merge($this->namespaces,$namespaces) as $k => $v){ + $ns_string .= " xmlns:$k=\"$v\""; + } + if($encodingStyle) { + $ns_string = " SOAP-ENV:encodingStyle=\"$encodingStyle\"$ns_string"; + } + + // serialize headers + if($headers){ + if (is_array($headers)) { + $xml = ''; + foreach ($headers as $header) { + $xml .= $this->serialize_val($header, false, false, false, false, false, $use); + } + $headers = $xml; + $this->debug("In serializeEnvelope, serialzied array of headers to $headers"); + } + $headers = "".$headers.""; + } + // serialize envelope + return + 'soap_defencoding .'"?'.">". + '". + $headers. + "". + $body. + "". + ""; + } + + /** + * formats a string to be inserted into an HTML stream + * + * @param string $str The string to format + * @return string The formatted string + * @access public + * @deprecated since at least TYPO3 4.3, will be removed in TYPO3 4.5 + */ + function formatDump($str){ + t3lib_div::logDeprecatedFunction(); + + $str = htmlspecialchars($str); + return nl2br($str); + } + + /** + * contracts (changes namespace to prefix) a qualified name + * + * @param string $qname qname + * @return string contracted qname + * @access private + */ + function contractQname($qname){ + // get element namespace + //$this->xdebug("Contract $qname"); + if (strrpos($qname, ':')) { + // get unqualified name + $name = substr($qname, strrpos($qname, ':') + 1); + // get ns + $ns = substr($qname, 0, strrpos($qname, ':')); + $p = $this->getPrefixFromNamespace($ns); + if ($p) { + return $p . ':' . $name; + } + return $qname; + } else { + return $qname; + } + } + + /** + * expands (changes prefix to namespace) a qualified name + * + * @param string $string qname + * @return string expanded qname + * @access private + */ + function expandQname($qname){ + // get element prefix + if(strpos($qname,':') && !preg_match('/^http:\/\//',$qname)){ + // get unqualified name + $name = substr(strstr($qname,':'),1); + // get ns prefix + $prefix = substr($qname,0,strpos($qname,':')); + if(isset($this->namespaces[$prefix])){ + return $this->namespaces[$prefix].':'.$name; + } else { + return $qname; + } + } else { + return $qname; + } + } + + /** + * returns the local part of a prefixed string + * returns the original string, if not prefixed + * + * @param string $str The prefixed string + * @return string The local part + * @access public + */ + function getLocalPart($str){ + if($sstr = strrchr($str,':')){ + // get unqualified name + return substr( $sstr, 1 ); + } else { + return $str; + } + } + + /** + * returns the prefix part of a prefixed string + * returns false, if not prefixed + * + * @param string $str The prefixed string + * @return mixed The prefix or false if there is no prefix + * @access public + */ + function getPrefix($str){ + if($pos = strrpos($str,':')){ + // get prefix + return substr($str,0,$pos); + } + return false; + } + + /** + * pass it a prefix, it returns a namespace + * + * @param string $prefix The prefix + * @return mixed The namespace, false if no namespace has the specified prefix + * @access public + */ + function getNamespaceFromPrefix($prefix){ + if (isset($this->namespaces[$prefix])) { + return $this->namespaces[$prefix]; + } + //$this->setError("No namespace registered for prefix '$prefix'"); + return false; + } + + /** + * returns the prefix for a given namespace (or prefix) + * or false if no prefixes registered for the given namespace + * + * @param string $ns The namespace + * @return mixed The prefix, false if the namespace has no prefixes + * @access public + */ + function getPrefixFromNamespace($ns) { + foreach ($this->namespaces as $p => $n) { + if ($ns == $n || $ns == $p) { + $this->usedNamespaces[$p] = $n; + return $p; + } + } + return false; + } + + /** + * returns the time in ODBC canonical form with microseconds + * + * @return string The time in ODBC canonical form with microseconds + * @access public + */ + function getmicrotime() { + if (function_exists('gettimeofday')) { + $tod = gettimeofday(); + $sec = $tod['sec']; + $usec = $tod['usec']; + } else { + $sec = time(); + $usec = 0; + } + return strftime('%Y-%m-%d %H:%M:%S', $sec) . '.' . sprintf('%06d', $usec); + } + + /** + * Returns a string with the output of var_dump + * + * @param mixed $data The variable to var_dump + * @return string The output of var_dump + * @access public + */ + function varDump($data) { + ob_start(); + var_dump($data); + $ret_val = ob_get_contents(); + ob_end_clean(); + return $ret_val; + } +} + +// XML Schema Datatype Helper Functions + +//xsd:dateTime helpers + +/** +* convert unix timestamp to ISO 8601 compliant date string +* +* @param string $timestamp Unix time stamp +* @access public +*/ +function timestamp_to_iso8601($timestamp,$utc=true){ + $datestr = date('Y-m-d\TH:i:sO',$timestamp); + if($utc){ + $pattern = '/'. + '([0-9]{4})-'. // centuries & years CCYY- + '([0-9]{2})-'. // months MM- + '([0-9]{2})'. // days DD + 'T'. // separator T + '([0-9]{2}):'. // hours hh: + '([0-9]{2}):'. // minutes mm: + '([0-9]{2})(\.[0-9]*)?'. // seconds ss.ss... + '(Z|[+\-][0-9]{2}:?[0-9]{2})?'. // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's + '/'; + + if(preg_match($pattern,$datestr,$regs)){ + return sprintf('%04d-%02d-%02dT%02d:%02d:%02dZ',$regs[1],$regs[2],$regs[3],$regs[4],$regs[5],$regs[6]); + } + return false; + } else { + return $datestr; + } +} + +/** +* convert ISO 8601 compliant date string to unix timestamp +* +* @param string $datestr ISO 8601 compliant date string +* @access public +*/ +function iso8601_to_timestamp($datestr){ + $pattern = '/'. + '([0-9]{4})-'. // centuries & years CCYY- + '([0-9]{2})-'. // months MM- + '([0-9]{2})'. // days DD + 'T'. // separator T + '([0-9]{2}):'. // hours hh: + '([0-9]{2}):'. // minutes mm: + '([0-9]{2})(\.[0-9]+)?'. // seconds ss.ss... + '(Z|[+\-][0-9]{2}:?[0-9]{2})?'. // Z to indicate UTC, -/+HH:MM:SS.SS... for local tz's + '/'; + + if(preg_match($pattern,$datestr,$regs)){ + // not utc + if($regs[8] != 'Z'){ + $op = substr($regs[8],0,1); + $h = substr($regs[8],1,2); + $m = substr($regs[8],strlen($regs[8])-2,2); + if($op == '-'){ + $regs[4] = $regs[4] + $h; + $regs[5] = $regs[5] + $m; + } elseif($op == '+'){ + $regs[4] = $regs[4] - $h; + $regs[5] = $regs[5] - $m; + } + } + return strtotime("$regs[1]-$regs[2]-$regs[3] $regs[4]:$regs[5]:$regs[6]Z"); + } else { + return false; + } +} + +/** +* sleeps some number of microseconds +* +* @param string $usec the number of microseconds to sleep +* @access public +* @deprecated since at least TYPO3 4.3, will be removed in TYPO3 4.5. +*/ +function usleepWindows($usec) +{ + t3lib_div::logDeprecatedFunction(); + + $start = gettimeofday(); + + do + { + $stop = gettimeofday(); + $timePassed = 1000000 * ($stop['sec'] - $start['sec']) + + $stop['usec'] - $start['usec']; + } + while ($timePassed < $usec); +} + + + +/** +* Contains information for a SOAP fault. +* Mainly used for returning faults from deployed functions +* in a server instance. +* @author Dietrich Ayala +* @version $Id: class.nusoap.php 6339 2009-11-05 21:21:54Z masi $ +* @access public +*/ +class soap_fault extends nusoap_base { + /** + * The fault code (client|server) + * @var string + * @access private + */ + var $faultcode; + /** + * The fault actor + * @var string + * @access private + */ + var $faultactor; + /** + * The fault string, a description of the fault + * @var string + * @access private + */ + var $faultstring; + /** + * The fault detail, typically a string or array of string + * @var mixed + * @access private + */ + var $faultdetail; + + /** + * constructor + * + * @param string $faultcode (client | server) + * @param string $faultactor only used when msg routed between multiple actors + * @param string $faultstring human readable error message + * @param mixed $faultdetail detail, typically a string or array of string + */ + function soap_fault($faultcode,$faultactor='',$faultstring='',$faultdetail=''){ + parent::nusoap_base(); + $this->faultcode = $faultcode; + $this->faultactor = $faultactor; + $this->faultstring = $faultstring; + $this->faultdetail = $faultdetail; + } + + /** + * serialize a fault + * + * @return string The serialization of the fault instance. + * @access public + */ + function serialize(){ + $ns_string = ''; + foreach($this->namespaces as $k => $v){ + $ns_string .= "\n xmlns:$k=\"$v\""; + } + $return_msg = + 'soap_defencoding.'"?>'. + '\n". + ''. + ''. + $this->serialize_val($this->faultcode, 'faultcode'). + $this->serialize_val($this->faultactor, 'faultactor'). + $this->serialize_val($this->faultstring, 'faultstring'). + $this->serialize_val($this->faultdetail, 'detail'). + ''. + ''. + ''; + return $return_msg; + } +} + + + +/** +* parses an XML Schema, allows access to it's data, other utility methods +* no validation... yet. +* very experimental and limited. As is discussed on XML-DEV, I'm one of the people +* that just doesn't have time to read the spec(s) thoroughly, and just have a couple of trusty +* tutorials I refer to :) +* +* @author Dietrich Ayala +* @version $Id: class.nusoap.php 6339 2009-11-05 21:21:54Z masi $ +* @access public +*/ +class XMLSchema extends nusoap_base { + + // files + var $schema = ''; + var $xml = ''; + // namespaces + var $enclosingNamespaces; + // schema info + var $schemaInfo = array(); + var $schemaTargetNamespace = ''; + // types, elements, attributes defined by the schema + var $attributes = array(); + var $complexTypes = array(); + var $complexTypeStack = array(); + var $currentComplexType = null; + var $elements = array(); + var $elementStack = array(); + var $currentElement = null; + var $simpleTypes = array(); + var $simpleTypeStack = array(); + var $currentSimpleType = null; + // imports + var $imports = array(); + // parser vars + var $parser; + var $position = 0; + var $depth = 0; + var $depth_array = array(); + var $message = array(); + var $defaultNamespace = array(); + + /** + * constructor + * + * @param string $schema schema document URI + * @param string $xml xml document URI + * @param string $namespaces namespaces defined in enclosing XML + * @access public + */ + function XMLSchema($schema='',$xml='',$namespaces=array()){ + parent::nusoap_base(); + $this->debug('xmlschema class instantiated, inside constructor'); + // files + $this->schema = $schema; + $this->xml = $xml; + + // namespaces + $this->enclosingNamespaces = $namespaces; + $this->namespaces = array_merge($this->namespaces, $namespaces); + + // parse schema file + if($schema != ''){ + $this->debug('initial schema file: '.$schema); + $this->parseFile($schema, 'schema'); + } + + // parse xml file + if($xml != ''){ + $this->debug('initial xml file: '.$xml); + $this->parseFile($xml, 'xml'); + } + + } + + /** + * parse an XML file + * + * @param string $xml, path/URL to XML file + * @param string $type, (schema | xml) + * @return boolean + * @access public + */ + function parseFile($xml,$type){ + // parse xml file + if($xml != ""){ + $xmlStr = @join("",@file($xml)); + if($xmlStr == ""){ + $msg = 'Error reading XML from '.$xml; + $this->setError($msg); + $this->debug($msg); + return false; + } else { + $this->debug("parsing $xml"); + $this->parseString($xmlStr,$type); + $this->debug("done parsing $xml"); + return true; + } + } + return false; + } + + /** + * parse an XML string + * + * @param string $xml path or URL + * @param string $type, (schema|xml) + * @access private + */ + function parseString($xml,$type){ + // parse xml string + if($xml != ""){ + + // Create an XML parser. + $this->parser = xml_parser_create(); + // Set the options for parsing the XML data. + xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0); + + // Set the object for the parser. + xml_set_object($this->parser, $this); + + // Set the element handlers for the parser. + if($type == "schema"){ + xml_set_element_handler($this->parser, 'schemaStartElement','schemaEndElement'); + xml_set_character_data_handler($this->parser,'schemaCharacterData'); + } elseif($type == "xml"){ + xml_set_element_handler($this->parser, 'xmlStartElement','xmlEndElement'); + xml_set_character_data_handler($this->parser,'xmlCharacterData'); + } + + // Parse the XML file. + if(!xml_parse($this->parser,$xml,true)){ + // Display an error message. + $errstr = sprintf('XML error parsing XML schema on line %d: %s', + xml_get_current_line_number($this->parser), + xml_error_string(xml_get_error_code($this->parser)) + ); + $this->debug($errstr); + $this->debug("XML payload:\n" . $xml); + $this->setError($errstr); + } + + xml_parser_free($this->parser); + } else{ + $this->debug('no xml passed to parseString()!!'); + $this->setError('no xml passed to parseString()!!'); + } + } + + /** + * start-element handler + * + * @param string $parser XML parser object + * @param string $name element name + * @param string $attrs associative array of attributes + * @access private + */ + function schemaStartElement($parser, $name, $attrs) { + + // position in the total number of elements, starting from 0 + $pos = $this->position++; + $depth = $this->depth++; + // set self as current value for this depth + $this->depth_array[$depth] = $pos; + $this->message[$pos] = array('cdata' => ''); + if ($depth > 0) { + $this->defaultNamespace[$pos] = $this->defaultNamespace[$this->depth_array[$depth - 1]]; + } else { + $this->defaultNamespace[$pos] = false; + } + + // get element prefix + if($prefix = $this->getPrefix($name)){ + // get unqualified name + $name = $this->getLocalPart($name); + } else { + $prefix = ''; + } + + // loop thru attributes, expanding, and registering namespace declarations + if(count($attrs) > 0){ + foreach($attrs as $k => $v){ + // if ns declarations, add to class level array of valid namespaces + if(preg_match('/^xmlns/',$k)){ + //$this->xdebug("$k: $v"); + //$this->xdebug('ns_prefix: '.$this->getPrefix($k)); + if($ns_prefix = substr(strrchr($k,':'),1)){ + //$this->xdebug("Add namespace[$ns_prefix] = $v"); + $this->namespaces[$ns_prefix] = $v; + } else { + $this->defaultNamespace[$pos] = $v; + if (! $this->getPrefixFromNamespace($v)) { + $this->namespaces['ns'.(count($this->namespaces)+1)] = $v; + } + } + if($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema' || $v == 'http://www.w3.org/2000/10/XMLSchema'){ + $this->XMLSchemaVersion = $v; + $this->namespaces['xsi'] = $v.'-instance'; + } + } + } + foreach($attrs as $k => $v){ + // expand each attribute + $k = strpos($k,':') ? $this->expandQname($k) : $k; + $v = strpos($v,':') ? $this->expandQname($v) : $v; + $eAttrs[$k] = $v; + } + $attrs = $eAttrs; + } else { + $attrs = array(); + } + // find status, register data + switch($name){ + case 'all': // (optional) compositor content for a complexType + case 'choice': + case 'group': + case 'sequence': + //$this->xdebug("compositor $name for currentComplexType: $this->currentComplexType and currentElement: $this->currentElement"); + $this->complexTypes[$this->currentComplexType]['compositor'] = $name; + //if($name == 'all' || $name == 'sequence'){ + // $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct'; + //} + break; + case 'attribute': // complexType attribute + //$this->xdebug("parsing attribute $attrs[name] $attrs[ref] of value: ".$attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']); + $this->xdebug("parsing attribute:"); + $this->appendDebug($this->varDump($attrs)); + if (!isset($attrs['form'])) { + $attrs['form'] = $this->schemaInfo['attributeFormDefault']; + } + if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) { + $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']; + if (!strpos($v, ':')) { + // no namespace in arrayType attribute value... + if ($this->defaultNamespace[$pos]) { + // ...so use the default + $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'] = $this->defaultNamespace[$pos] . ':' . $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']; + } + } + } + if(isset($attrs['name'])){ + $this->attributes[$attrs['name']] = $attrs; + $aname = $attrs['name']; + } elseif(isset($attrs['ref']) && $attrs['ref'] == 'http://schemas.xmlsoap.org/soap/encoding/:arrayType'){ + if (isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])) { + $aname = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']; + } else { + $aname = ''; + } + } elseif(isset($attrs['ref'])){ + $aname = $attrs['ref']; + $this->attributes[$attrs['ref']] = $attrs; + } + + if($this->currentComplexType){ // This should *always* be + $this->complexTypes[$this->currentComplexType]['attrs'][$aname] = $attrs; + } + // arrayType attribute + if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']) || $this->getLocalPart($aname) == 'arrayType'){ + $this->complexTypes[$this->currentComplexType]['phpType'] = 'array'; + $prefix = $this->getPrefix($aname); + if(isset($attrs['http://schemas.xmlsoap.org/wsdl/:arrayType'])){ + $v = $attrs['http://schemas.xmlsoap.org/wsdl/:arrayType']; + } else { + $v = ''; + } + if(strpos($v,'[,]')){ + $this->complexTypes[$this->currentComplexType]['multidimensional'] = true; + } + $v = substr($v,0,strpos($v,'[')); // clip the [] + if(!strpos($v,':') && isset($this->typemap[$this->XMLSchemaVersion][$v])){ + $v = $this->XMLSchemaVersion.':'.$v; + } + $this->complexTypes[$this->currentComplexType]['arrayType'] = $v; + } + break; + case 'complexContent': // (optional) content for a complexType + break; + case 'complexType': + array_push($this->complexTypeStack, $this->currentComplexType); + if(isset($attrs['name'])){ + $this->xdebug('processing named complexType '.$attrs['name']); + //$this->currentElement = false; + $this->currentComplexType = $attrs['name']; + $this->complexTypes[$this->currentComplexType] = $attrs; + $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType'; + // This is for constructs like + // + // + // + // + // + if(isset($attrs['base']) && preg_match('/:Array$/',$attrs['base'])){ + $this->xdebug('complexType is unusual array'); + $this->complexTypes[$this->currentComplexType]['phpType'] = 'array'; + } else { + $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct'; + } + }else{ + $this->xdebug('processing unnamed complexType for element '.$this->currentElement); + $this->currentComplexType = $this->currentElement . '_ContainedType'; + //$this->currentElement = false; + $this->complexTypes[$this->currentComplexType] = $attrs; + $this->complexTypes[$this->currentComplexType]['typeClass'] = 'complexType'; + // This is for constructs like + // + // + // + // + // + if(isset($attrs['base']) && preg_match('/:Array$/',$attrs['base'])){ + $this->xdebug('complexType is unusual array'); + $this->complexTypes[$this->currentComplexType]['phpType'] = 'array'; + } else { + $this->complexTypes[$this->currentComplexType]['phpType'] = 'struct'; + } + } + break; + case 'element': + array_push($this->elementStack, $this->currentElement); + // elements defined as part of a complex type should + // not really be added to $this->elements, but for some + // reason, they are + if (!isset($attrs['form'])) { + $attrs['form'] = $this->schemaInfo['elementFormDefault']; + } + if(isset($attrs['type'])){ + $this->xdebug("processing typed element ".$attrs['name']." of type ".$attrs['type']); + if (! $this->getPrefix($attrs['type'])) { + if ($this->defaultNamespace[$pos]) { + $attrs['type'] = $this->defaultNamespace[$pos] . ':' . $attrs['type']; + $this->xdebug('used default namespace to make type ' . $attrs['type']); + } + } + // This is for constructs like + // + // + // + // + // + if ($this->currentComplexType && $this->complexTypes[$this->currentComplexType]['phpType'] == 'array') { + $this->xdebug('arrayType for unusual array is ' . $attrs['type']); + $this->complexTypes[$this->currentComplexType]['arrayType'] = $attrs['type']; + } + $this->currentElement = $attrs['name']; + $this->elements[ $attrs['name'] ] = $attrs; + $this->elements[ $attrs['name'] ]['typeClass'] = 'element'; + $ename = $attrs['name']; + } elseif(isset($attrs['ref'])){ + $this->xdebug("processing element as ref to ".$attrs['ref']); + $this->currentElement = "ref to ".$attrs['ref']; + $ename = $this->getLocalPart($attrs['ref']); + } else { + $this->xdebug("processing untyped element ".$attrs['name']); + $this->currentElement = $attrs['name']; + $this->elements[ $attrs['name'] ] = $attrs; + $this->elements[ $attrs['name'] ]['typeClass'] = 'element'; + $attrs['type'] = $this->schemaTargetNamespace . ':' . $attrs['name'] . '_ContainedType'; + $this->elements[ $attrs['name'] ]['type'] = $attrs['type']; + $ename = $attrs['name']; + } + if(isset($ename) && $this->currentComplexType){ + $this->complexTypes[$this->currentComplexType]['elements'][$ename] = $attrs; + } + break; + case 'enumeration': // restriction value list member + $this->xdebug('enumeration ' . $attrs['value']); + if ($this->currentSimpleType) { + $this->simpleTypes[$this->currentSimpleType]['enumeration'][] = $attrs['value']; + } elseif ($this->currentComplexType) { + $this->complexTypes[$this->currentComplexType]['enumeration'][] = $attrs['value']; + } + break; + case 'extension': // simpleContent or complexContent type extension + $this->xdebug('extension ' . $attrs['base']); + if ($this->currentComplexType) { + $this->complexTypes[$this->currentComplexType]['extensionBase'] = $attrs['base']; + } + break; + case 'import': + if (isset($attrs['schemaLocation'])) { + //$this->xdebug('import namespace ' . $attrs['namespace'] . ' from ' . $attrs['schemaLocation']); + $this->imports[$attrs['namespace']][] = array('location' => $attrs['schemaLocation'], 'loaded' => false); + } else { + //$this->xdebug('import namespace ' . $attrs['namespace']); + $this->imports[$attrs['namespace']][] = array('location' => '', 'loaded' => true); + if (! $this->getPrefixFromNamespace($attrs['namespace'])) { + $this->namespaces['ns'.(count($this->namespaces)+1)] = $attrs['namespace']; + } + } + break; + case 'list': // simpleType value list + break; + case 'restriction': // simpleType, simpleContent or complexContent value restriction + $this->xdebug('restriction ' . $attrs['base']); + if($this->currentSimpleType){ + $this->simpleTypes[$this->currentSimpleType]['type'] = $attrs['base']; + } elseif($this->currentComplexType){ + $this->complexTypes[$this->currentComplexType]['restrictionBase'] = $attrs['base']; + if(strstr($attrs['base'],':') == ':Array'){ + $this->complexTypes[$this->currentComplexType]['phpType'] = 'array'; + } + } + break; + case 'schema': + $this->schemaInfo = $attrs; + $this->schemaInfo['schemaVersion'] = $this->getNamespaceFromPrefix($prefix); + if (isset($attrs['targetNamespace'])) { + $this->schemaTargetNamespace = $attrs['targetNamespace']; + } + if (!isset($attrs['elementFormDefault'])) { + $this->schemaInfo['elementFormDefault'] = 'unqualified'; + } + if (!isset($attrs['attributeFormDefault'])) { + $this->schemaInfo['attributeFormDefault'] = 'unqualified'; + } + break; + case 'simpleContent': // (optional) content for a complexType + break; + case 'simpleType': + array_push($this->simpleTypeStack, $this->currentSimpleType); + if(isset($attrs['name'])){ + $this->xdebug("processing simpleType for name " . $attrs['name']); + $this->currentSimpleType = $attrs['name']; + $this->simpleTypes[ $attrs['name'] ] = $attrs; + $this->simpleTypes[ $attrs['name'] ]['typeClass'] = 'simpleType'; + $this->simpleTypes[ $attrs['name'] ]['phpType'] = 'scalar'; + } else { + $this->xdebug('processing unnamed simpleType for element '.$this->currentElement); + $this->currentSimpleType = $this->currentElement . '_ContainedType'; + //$this->currentElement = false; + $this->simpleTypes[$this->currentSimpleType] = $attrs; + $this->simpleTypes[$this->currentSimpleType]['phpType'] = 'scalar'; + } + break; + case 'union': // simpleType type list + break; + default: + //$this->xdebug("do not have anything to do for element $name"); + } + } + + /** + * end-element handler + * + * @param string $parser XML parser object + * @param string $name element name + * @access private + */ + function schemaEndElement($parser, $name) { + // bring depth down a notch + $this->depth--; + // position of current element is equal to the last value left in depth_array for my depth + if(isset($this->depth_array[$this->depth])){ + $pos = $this->depth_array[$this->depth]; + } + // get element prefix + if ($prefix = $this->getPrefix($name)){ + // get unqualified name + $name = $this->getLocalPart($name); + } else { + $prefix = ''; + } + // move on... + if($name == 'complexType'){ + $this->xdebug('done processing complexType ' . ($this->currentComplexType ? $this->currentComplexType : '(unknown)')); + $this->currentComplexType = array_pop($this->complexTypeStack); + //$this->currentElement = false; + } + if($name == 'element'){ + $this->xdebug('done processing element ' . ($this->currentElement ? $this->currentElement : '(unknown)')); + $this->currentElement = array_pop($this->elementStack); + } + if($name == 'simpleType'){ + $this->xdebug('done processing simpleType ' . ($this->currentSimpleType ? $this->currentSimpleType : '(unknown)')); + $this->currentSimpleType = array_pop($this->simpleTypeStack); + } + } + + /** + * element content handler + * + * @param string $parser XML parser object + * @param string $data element content + * @access private + */ + function schemaCharacterData($parser, $data){ + $pos = $this->depth_array[$this->depth - 1]; + $this->message[$pos]['cdata'] .= $data; + } + + /** + * serialize the schema + * + * @access public + */ + function serializeSchema(){ + + $schemaPrefix = $this->getPrefixFromNamespace($this->XMLSchemaVersion); + $xml = ''; + // imports + if (sizeof($this->imports) > 0) { + foreach($this->imports as $ns => $list) { + foreach ($list as $ii) { + if ($ii['location'] != '') { + $xml .= " <$schemaPrefix:import location=\"" . $ii['location'] . '" namespace="' . $ns . "\" />\n"; + } else { + $xml .= " <$schemaPrefix:import namespace=\"" . $ns . "\" />\n"; + } + } + } + } + // complex types + foreach($this->complexTypes as $typeName => $attrs){ + $contentStr = ''; + // serialize child elements + if(isset($attrs['elements']) && (count($attrs['elements']) > 0)){ + foreach($attrs['elements'] as $element => $eParts){ + if(isset($eParts['ref'])){ + $contentStr .= " <$schemaPrefix:element ref=\"$element\"/>\n"; + } else { + $contentStr .= " <$schemaPrefix:element name=\"$element\" type=\"" . $this->contractQName($eParts['type']) . "\""; + foreach ($eParts as $aName => $aValue) { + // handle, e.g., abstract, default, form, minOccurs, maxOccurs, nillable + if ($aName != 'name' && $aName != 'type') { + $contentStr .= " $aName=\"$aValue\""; + } + } + $contentStr .= "/>\n"; + } + } + // compositor wraps elements + if (isset($attrs['compositor']) && ($attrs['compositor'] != '')) { + $contentStr = " <$schemaPrefix:$attrs[compositor]>\n".$contentStr." \n"; + } + } + // attributes + if(isset($attrs['attrs']) && (count($attrs['attrs']) >= 1)){ + foreach($attrs['attrs'] as $attr => $aParts){ + $contentStr .= " <$schemaPrefix:attribute"; + foreach ($aParts as $a => $v) { + if ($a == 'ref' || $a == 'type') { + $contentStr .= " $a=\"".$this->contractQName($v).'"'; + } elseif ($a == 'http://schemas.xmlsoap.org/wsdl/:arrayType') { + $this->usedNamespaces['wsdl'] = $this->namespaces['wsdl']; + $contentStr .= ' wsdl:arrayType="'.$this->contractQName($v).'"'; + } else { + $contentStr .= " $a=\"$v\""; + } + } + $contentStr .= "/>\n"; + } + } + // if restriction + if (isset($attrs['restrictionBase']) && $attrs['restrictionBase'] != ''){ + $contentStr = " <$schemaPrefix:restriction base=\"".$this->contractQName($attrs['restrictionBase'])."\">\n".$contentStr." \n"; + // complex or simple content + if ((isset($attrs['elements']) && count($attrs['elements']) > 0) || (isset($attrs['attrs']) && count($attrs['attrs']) > 0)){ + $contentStr = " <$schemaPrefix:complexContent>\n".$contentStr." \n"; + } + } + // finalize complex type + if($contentStr != ''){ + $contentStr = " <$schemaPrefix:complexType name=\"$typeName\">\n".$contentStr." \n"; + } else { + $contentStr = " <$schemaPrefix:complexType name=\"$typeName\"/>\n"; + } + $xml .= $contentStr; + } + // simple types + if(isset($this->simpleTypes) && count($this->simpleTypes) > 0){ + foreach($this->simpleTypes as $typeName => $eParts){ + $xml .= " <$schemaPrefix:simpleType name=\"$typeName\">\n <$schemaPrefix:restriction base=\"".$this->contractQName($eParts['type'])."\"/>\n"; + if (isset($eParts['enumeration'])) { + foreach ($eParts['enumeration'] as $e) { + $xml .= " <$schemaPrefix:enumeration value=\"$e\"/>\n"; + } + } + $xml .= " "; + } + } + // elements + if(isset($this->elements) && count($this->elements) > 0){ + foreach($this->elements as $element => $eParts){ + $xml .= " <$schemaPrefix:element name=\"$element\" type=\"".$this->contractQName($eParts['type'])."\"/>\n"; + } + } + // attributes + if(isset($this->attributes) && count($this->attributes) > 0){ + foreach($this->attributes as $attr => $aParts){ + $xml .= " <$schemaPrefix:attribute name=\"$attr\" type=\"".$this->contractQName($aParts['type'])."\"\n/>"; + } + } + // finish 'er up + $el = "<$schemaPrefix:schema targetNamespace=\"$this->schemaTargetNamespace\"\n"; + foreach (array_diff($this->usedNamespaces, $this->enclosingNamespaces) as $nsp => $ns) { + $el .= " xmlns:$nsp=\"$ns\"\n"; + } + $xml = $el . ">\n".$xml."\n"; + return $xml; + } + + /** + * adds debug data to the clas level debug string + * + * @param string $string debug data + * @access private + */ + function xdebug($string){ + $this->debug('<' . $this->schemaTargetNamespace . '> '.$string); + } + + /** + * get the PHP type of a user defined type in the schema + * PHP type is kind of a misnomer since it actually returns 'struct' for assoc. arrays + * returns false if no type exists, or not w/ the given namespace + * else returns a string that is either a native php type, or 'struct' + * + * @param string $type, name of defined type + * @param string $ns, namespace of type + * @return mixed + * @access public + * @deprecated since at least TYPO3 4.3, will be removed in TYPO3 4.5. + */ + function getPHPType($type,$ns){ + t3lib_div::logDeprecatedFunction(); + + if(isset($this->typemap[$ns][$type])){ + //print "found type '$type' and ns $ns in typemap
"; + return $this->typemap[$ns][$type]; + } elseif(isset($this->complexTypes[$type])){ + //print "getting type '$type' and ns $ns from complexTypes array
"; + return $this->complexTypes[$type]['phpType']; + } + return false; + } + + /** + * returns an associative array of information about a given type + * returns false if no type exists by the given name + * + * For a complexType typeDef = array( + * 'restrictionBase' => '', + * 'phpType' => '', + * 'compositor' => '(sequence|all)', + * 'elements' => array(), // refs to elements array + * 'attrs' => array() // refs to attributes array + * ... and so on (see addComplexType) + * ) + * + * For simpleType or element, the array has different keys. + * + * @param string + * @return mixed + * @access public + * @see addComplexType + * @see addSimpleType + * @see addElement + */ + function getTypeDef($type){ + //$this->debug("in getTypeDef for type $type"); + if(isset($this->complexTypes[$type])){ + $this->xdebug("in getTypeDef, found complexType $type"); + return $this->complexTypes[$type]; + } elseif(isset($this->simpleTypes[$type])){ + $this->xdebug("in getTypeDef, found simpleType $type"); + if (!isset($this->simpleTypes[$type]['phpType'])) { + // get info for type to tack onto the simple type + // TODO: can this ever really apply (i.e. what is a simpleType really?) + $uqType = substr($this->simpleTypes[$type]['type'], strrpos($this->simpleTypes[$type]['type'], ':') + 1); + $ns = substr($this->simpleTypes[$type]['type'], 0, strrpos($this->simpleTypes[$type]['type'], ':')); + $etype = $this->getTypeDef($uqType); + if ($etype) { + $this->xdebug("in getTypeDef, found type for simpleType $type:"); + $this->xdebug($this->varDump($etype)); + if (isset($etype['phpType'])) { + $this->simpleTypes[$type]['phpType'] = $etype['phpType']; + } + if (isset($etype['elements'])) { + $this->simpleTypes[$type]['elements'] = $etype['elements']; + } + } + } + return $this->simpleTypes[$type]; + } elseif(isset($this->elements[$type])){ + $this->xdebug("in getTypeDef, found element $type"); + if (!isset($this->elements[$type]['phpType'])) { + // get info for type to tack onto the element + $uqType = substr($this->elements[$type]['type'], strrpos($this->elements[$type]['type'], ':') + 1); + $ns = substr($this->elements[$type]['type'], 0, strrpos($this->elements[$type]['type'], ':')); + $etype = $this->getTypeDef($uqType); + if ($etype) { + $this->xdebug("in getTypeDef, found type for element $type:"); + $this->xdebug($this->varDump($etype)); + if (isset($etype['phpType'])) { + $this->elements[$type]['phpType'] = $etype['phpType']; + } + if (isset($etype['elements'])) { + $this->elements[$type]['elements'] = $etype['elements']; + } + } elseif ($ns == 'http://www.w3.org/2001/XMLSchema') { + $this->xdebug("in getTypeDef, element $type is an XSD type"); + $this->elements[$type]['phpType'] = 'scalar'; + } + } + return $this->elements[$type]; + } elseif(isset($this->attributes[$type])){ + $this->xdebug("in getTypeDef, found attribute $type"); + return $this->attributes[$type]; + } elseif (preg_match('/_ContainedType$/', $type)) { + $this->xdebug("in getTypeDef, have an untyped element $type"); + $typeDef['typeClass'] = 'simpleType'; + $typeDef['phpType'] = 'scalar'; + $typeDef['type'] = 'http://www.w3.org/2001/XMLSchema:string'; + return $typeDef; + } + $this->xdebug("in getTypeDef, did not find $type"); + return false; + } + + /** + * returns a sample serialization of a given type, or false if no type by the given name + * + * @param string $type, name of type + * @return mixed + * @access public + * @deprecated since at least TYPO3 4.3, will be removed in TYPO3 4.5. + */ + function serializeTypeDef($type){ + t3lib_div::logDeprecatedFunction(); + + //print "in sTD() for type $type
"; + if($typeDef = $this->getTypeDef($type)){ + $str .= '<'.$type; + if(is_array($typeDef['attrs'])){ + foreach($attrs as $attName => $data){ + $str .= " $attName=\"{type = ".$data['type']."}\""; + } + } + $str .= " xmlns=\"".$this->schema['targetNamespace']."\""; + if(count($typeDef['elements']) > 0){ + $str .= ">"; + foreach($typeDef['elements'] as $element => $eData){ + $str .= $this->serializeTypeDef($element); + } + $str .= ""; + } elseif($typeDef['typeClass'] == 'element') { + $str .= ">"; + } else { + $str .= "/>"; + } + return $str; + } + return false; + } + + /** + * returns HTML form elements that allow a user + * to enter values for creating an instance of the given type. + * + * @param string $name, name for type instance + * @param string $type, name of type + * @return string + * @access public + * @deprecated since at least TYPO3 4.3, will be removed in TYPO3 4.5. + */ + function typeToForm($name,$type){ + t3lib_div::logDeprecatedFunction(); + + // get typedef + if($typeDef = $this->getTypeDef($type)){ + // if struct + if($typeDef['phpType'] == 'struct'){ + $buffer .= ''; + foreach($typeDef['elements'] as $child => $childDef){ + $buffer .= " + + "; + } + $buffer .= '
$childDef[name] (type: ".$this->getLocalPart($childDef['type'])."):
'; + // if array + } elseif($typeDef['phpType'] == 'array'){ + $buffer .= ''; + for($i=0;$i < 3; $i++){ + $buffer .= " + + "; + } + $buffer .= '
array item (type: $typeDef[arrayType]):
'; + // if scalar + } else { + $buffer .= ""; + } + } else { + $buffer .= ""; + } + return $buffer; + } + + /** + * adds a complex type to the schema + * + * example: array + * + * addType( + * 'ArrayOfstring', + * 'complexType', + * 'array', + * '', + * 'SOAP-ENC:Array', + * array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'string[]'), + * 'xsd:string' + * ); + * + * example: PHP associative array ( SOAP Struct ) + * + * addType( + * 'SOAPStruct', + * 'complexType', + * 'struct', + * 'all', + * array('myVar'=> array('name'=>'myVar','type'=>'string') + * ); + * + * @param name + * @param typeClass (complexType|simpleType|attribute) + * @param phpType: currently supported are array and struct (php assoc array) + * @param compositor (all|sequence|choice) + * @param restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array) + * @param elements = array ( name = array(name=>'',type=>'') ) + * @param attrs = array( + * array( + * 'ref' => "http://schemas.xmlsoap.org/soap/encoding/:arrayType", + * "http://schemas.xmlsoap.org/wsdl/:arrayType" => "string[]" + * ) + * ) + * @param arrayType: namespace:name (http://www.w3.org/2001/XMLSchema:string) + * @access public + * @see getTypeDef + */ + function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType=''){ + $this->complexTypes[$name] = array( + 'name' => $name, + 'typeClass' => $typeClass, + 'phpType' => $phpType, + 'compositor'=> $compositor, + 'restrictionBase' => $restrictionBase, + 'elements' => $elements, + 'attrs' => $attrs, + 'arrayType' => $arrayType + ); + + $this->xdebug("addComplexType $name:"); + $this->appendDebug($this->varDump($this->complexTypes[$name])); + } + + /** + * adds a simple type to the schema + * + * @param string $name + * @param string $restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array) + * @param string $typeClass (should always be simpleType) + * @param string $phpType (should always be scalar) + * @param array $enumeration array of values + * @access public + * @see xmlschema + * @see getTypeDef + */ + function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar', $enumeration=array()) { + $this->simpleTypes[$name] = array( + 'name' => $name, + 'typeClass' => $typeClass, + 'phpType' => $phpType, + 'type' => $restrictionBase, + 'enumeration' => $enumeration + ); + + $this->xdebug("addSimpleType $name:"); + $this->appendDebug($this->varDump($this->simpleTypes[$name])); + } + + /** + * adds an element to the schema + * + * @param array $attrs attributes that must include name and type + * @see xmlschema + * @access public + */ + function addElement($attrs) { + if (! $this->getPrefix($attrs['type'])) { + $attrs['type'] = $this->schemaTargetNamespace . ':' . $attrs['type']; + } + $this->elements[ $attrs['name'] ] = $attrs; + $this->elements[ $attrs['name'] ]['typeClass'] = 'element'; + + $this->xdebug("addElement " . $attrs['name']); + $this->appendDebug($this->varDump($this->elements[ $attrs['name'] ])); + } +} + + + +/** +* For creating serializable abstractions of native PHP types. This class +* allows element name/namespace, XSD type, and XML attributes to be +* associated with a value. This is extremely useful when WSDL is not +* used, but is also useful when WSDL is used with polymorphic types, including +* xsd:anyType and user-defined types. +* +* @author Dietrich Ayala +* @version $Id: class.nusoap.php 6339 2009-11-05 21:21:54Z masi $ +* @access public +*/ +class soapval extends nusoap_base { + /** + * The XML element name + * + * @var string + * @access private + */ + var $name; + /** + * The XML type name (string or false) + * + * @var mixed + * @access private + */ + var $type; + /** + * The PHP value + * + * @var mixed + * @access private + */ + var $value; + /** + * The XML element namespace (string or false) + * + * @var mixed + * @access private + */ + var $element_ns; + /** + * The XML type namespace (string or false) + * + * @var mixed + * @access private + */ + var $type_ns; + /** + * The XML element attributes (array or false) + * + * @var mixed + * @access private + */ + var $attributes; + + /** + * constructor + * + * @param string $name optional name + * @param mixed $type optional type name + * @param mixed $value optional value + * @param mixed $element_ns optional namespace of value + * @param mixed $type_ns optional namespace of type + * @param mixed $attributes associative array of attributes to add to element serialization + * @access public + */ + function soapval($name='soapval',$type=false,$value=-1,$element_ns=false,$type_ns=false,$attributes=false) { + parent::nusoap_base(); + $this->name = $name; + $this->type = $type; + $this->value = $value; + $this->element_ns = $element_ns; + $this->type_ns = $type_ns; + $this->attributes = $attributes; + } + + /** + * return serialized value + * + * @param string $use The WSDL use value (encoded|literal) + * @return string XML data + * @access public + */ + function serialize($use='encoded') { + return $this->serialize_val($this->value,$this->name,$this->type,$this->element_ns,$this->type_ns,$this->attributes,$use); + } + + /** + * decodes a soapval object into a PHP native type + * + * @return mixed + * @access public + */ + function decode(){ + return $this->value; + } +} + + + +/** +* transport class for sending/receiving data via HTTP and HTTPS +* NOTE: PHP must be compiled with the CURL extension for HTTPS support +* +* @author Dietrich Ayala +* @version $Id: class.nusoap.php 6339 2009-11-05 21:21:54Z masi $ +* @access public +*/ +class soap_transport_http extends nusoap_base { + + var $url = ''; + var $uri = ''; + var $digest_uri = ''; + var $scheme = ''; + var $host = ''; + var $port = ''; + var $path = ''; + var $request_method = 'POST'; + var $protocol_version = '1.0'; + var $encoding = ''; + var $outgoing_headers = array(); + var $incoming_headers = array(); + var $incoming_cookies = array(); + var $outgoing_payload = ''; + var $incoming_payload = ''; + var $useSOAPAction = true; + var $persistentConnection = false; + var $ch = false; // cURL handle + var $username = ''; + var $password = ''; + var $authtype = ''; + var $digestRequest = array(); + var $certRequest = array(); // keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional) + // cainfofile: certificate authority file, e.g. '$pathToPemFiles/rootca.pem' + // sslcertfile: SSL certificate file, e.g. '$pathToPemFiles/mycert.pem' + // sslkeyfile: SSL key file, e.g. '$pathToPemFiles/mykey.pem' + // passphrase: SSL key password/passphrase + // verifypeer: default is 1 + // verifyhost: default is 1 + + /** + * constructor + */ + function soap_transport_http($url){ + parent::nusoap_base(); + $this->setURL($url); + preg_match('/\$Revisio' . 'n: ([^ ]+)/', $this->revision, $rev); + $this->outgoing_headers['User-Agent'] = $this->title.'/'.$this->version.' ('.$rev[1].')'; + $this->debug('set User-Agent: ' . $this->outgoing_headers['User-Agent']); + } + + function setURL($url) { + $this->url = $url; + + $u = parse_url($url); + foreach($u as $k => $v){ + $this->debug("$k = $v"); + $this->$k = $v; + } + + // add any GET params to path + if(isset($u['query']) && $u['query'] != ''){ + $this->path .= '?' . $u['query']; + } + + // set default port + if(!isset($u['port'])){ + if($u['scheme'] == 'https'){ + $this->port = 443; + } else { + $this->port = 80; + } + } + + $this->uri = $this->path; + $this->digest_uri = $this->uri; + + // build headers + if (!isset($u['port'])) { + $this->outgoing_headers['Host'] = $this->host; + } else { + $this->outgoing_headers['Host'] = $this->host.':'.$this->port; + } + $this->debug('set Host: ' . $this->outgoing_headers['Host']); + + if (isset($u['user']) && $u['user'] != '') { + $this->setCredentials(urldecode($u['user']), isset($u['pass']) ? urldecode($u['pass']) : ''); + } + } + + function connect($connection_timeout=0,$response_timeout=30){ + // For PHP 4.3 with OpenSSL, change https scheme to ssl, then treat like + // "regular" socket. + // TODO: disabled for now because OpenSSL must be *compiled* in (not just + // loaded), and until PHP5 stream_get_wrappers is not available. +// if ($this->scheme == 'https') { +// if (version_compare(phpversion(), '4.3.0') >= 0) { +// if (extension_loaded('openssl')) { +// $this->scheme = 'ssl'; +// $this->debug('Using SSL over OpenSSL'); +// } +// } +// } + $this->debug("connect connection_timeout $connection_timeout, response_timeout $response_timeout, scheme $this->scheme, host $this->host, port $this->port"); + if ($this->scheme == 'http' || $this->scheme == 'ssl') { + // use persistent connection + if($this->persistentConnection && isset($this->fp) && is_resource($this->fp)){ + if (!feof($this->fp)) { + $this->debug('Re-use persistent connection'); + return true; + } + fclose($this->fp); + $this->debug('Closed persistent connection at EOF'); + } + + // munge host if using OpenSSL + if ($this->scheme == 'ssl') { + $host = 'ssl://' . $this->host; + } else { + $host = $this->host; + } + $this->debug('calling fsockopen with host ' . $host . ' connection_timeout ' . $connection_timeout); + + // open socket + if($connection_timeout > 0){ + $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str, $connection_timeout); + } else { + $this->fp = @fsockopen( $host, $this->port, $this->errno, $this->error_str); + } + + // test pointer + if(!$this->fp) { + $msg = 'Couldn\'t open socket connection to server ' . $this->url; + if ($this->errno) { + $msg .= ', Error ('.$this->errno.'): '.$this->error_str; + } else { + $msg .= ' prior to connect(). This is often a problem looking up the host name.'; + } + $this->debug($msg); + $this->setError($msg); + return false; + } + + // set response timeout + $this->debug('set response timeout to ' . $response_timeout); + socket_set_timeout( $this->fp, $response_timeout); + + $this->debug('socket connected'); + return true; + } else if ($this->scheme == 'https') { + if (!extension_loaded('curl')) { + $this->setError('CURL Extension, or OpenSSL extension w/ PHP version >= 4.3 is required for HTTPS'); + return false; + } + $this->debug('connect using https'); + // init CURL + $this->ch = curl_init(); + // set url + $hostURL = ($this->port != '') ? "https://$this->host:$this->port" : "https://$this->host"; + // add path + $hostURL .= $this->path; + curl_setopt($this->ch, CURLOPT_URL, $hostURL); + // follow location headers (re-directs) + curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, 1); + // ask for headers in the response output + curl_setopt($this->ch, CURLOPT_HEADER, 1); + // ask for the response output as the return value + curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1); + // encode + // We manage this ourselves through headers and encoding +// if(function_exists('gzuncompress')){ +// curl_setopt($this->ch, CURLOPT_ENCODING, 'deflate'); +// } + // persistent connection + if ($this->persistentConnection) { + // The way we send data, we cannot use persistent connections, since + // there will be some "junk" at the end of our request. + //curl_setopt($this->ch, CURL_HTTP_VERSION_1_1, true); + $this->persistentConnection = false; + $this->outgoing_headers['Connection'] = 'close'; + $this->debug('set Connection: ' . $this->outgoing_headers['Connection']); + } + // set timeout + if ($connection_timeout != 0) { + curl_setopt($this->ch, CURLOPT_TIMEOUT, $connection_timeout); + } + // TODO: cURL has added a connection timeout separate from the response timeout + //if ($connection_timeout != 0) { + // curl_setopt($this->ch, CURLOPT_CONNECTIONTIMEOUT, $connection_timeout); + //} + //if ($response_timeout != 0) { + // curl_setopt($this->ch, CURLOPT_TIMEOUT, $response_timeout); + //} + + // recent versions of cURL turn on peer/host checking by default, + // while PHP binaries are not compiled with a default location for the + // CA cert bundle, so disable peer/host checking. +//curl_setopt($this->ch, CURLOPT_CAINFO, 'f:\php-4.3.2-win32\extensions\curl-ca-bundle.crt'); + curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 0); + curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 0); + + // support client certificates (thanks Tobias Boes, Doug Anarino, Eryan Ariobowo) + if ($this->authtype == 'certificate') { + if (isset($this->certRequest['cainfofile'])) { + curl_setopt($this->ch, CURLOPT_CAINFO, $this->certRequest['cainfofile']); + } + if (isset($this->certRequest['verifypeer'])) { + curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, $this->certRequest['verifypeer']); + } else { + curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 1); + } + if (isset($this->certRequest['verifyhost'])) { + curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, $this->certRequest['verifyhost']); + } else { + curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 1); + } + if (isset($this->certRequest['sslcertfile'])) { + curl_setopt($this->ch, CURLOPT_SSLCERT, $this->certRequest['sslcertfile']); + } + if (isset($this->certRequest['sslkeyfile'])) { + curl_setopt($this->ch, CURLOPT_SSLKEY, $this->certRequest['sslkeyfile']); + } + if (isset($this->certRequest['passphrase'])) { + curl_setopt($this->ch, CURLOPT_SSLKEYPASSWD , $this->certRequest['passphrase']); + } + } + $this->debug('cURL connection set up'); + return true; + } else { + $this->setError('Unknown scheme ' . $this->scheme); + $this->debug('Unknown scheme ' . $this->scheme); + return false; + } + } + + /** + * send the SOAP message via HTTP + * + * @param string $data message data + * @param integer $timeout set connection timeout in seconds + * @param integer $response_timeout set response timeout in seconds + * @param array $cookies cookies to send + * @return string data + * @access public + */ + function send($data, $timeout=0, $response_timeout=30, $cookies=NULL) { + + $this->debug('entered send() with data of length: '.strlen($data)); + + $this->tryagain = true; + $tries = 0; + while ($this->tryagain) { + $this->tryagain = false; + if ($tries++ < 2) { + // make connnection + if (!$this->connect($timeout, $response_timeout)){ + return false; + } + + // send request + if (!$this->sendRequest($data, $cookies)){ + return false; + } + + // get response + $respdata = $this->getResponse(); + } else { + $this->setError('Too many tries to get an OK response'); + } + } + $this->debug('end of send()'); + return $respdata; + } + + + /** + * send the SOAP message via HTTPS 1.0 using CURL + * + * @param string $msg message data + * @param integer $timeout set connection timeout in seconds + * @param integer $response_timeout set response timeout in seconds + * @param array $cookies cookies to send + * @return string data + * @access public + */ + function sendHTTPS($data, $timeout=0, $response_timeout=30, $cookies) { + return $this->send($data, $timeout, $response_timeout, $cookies); + } + + /** + * if authenticating, set user credentials here + * + * @param string $username + * @param string $password + * @param string $authtype (basic, digest, certificate) + * @param array $digestRequest (keys must be nonce, nc, realm, qop) + * @param array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs) + * @access public + */ + function setCredentials($username, $password, $authtype = 'basic', $digestRequest = array(), $certRequest = array()) { + $this->debug("Set credentials for authtype $authtype"); + // cf. RFC 2617 + if ($authtype == 'basic') { + $this->outgoing_headers['Authorization'] = 'Basic '.base64_encode(str_replace(':','',$username).':'.$password); + } elseif ($authtype == 'digest') { + if (isset($digestRequest['nonce'])) { + $digestRequest['nc'] = isset($digestRequest['nc']) ? $digestRequest['nc']++ : 1; + + // calculate the Digest hashes (calculate code based on digest implementation found at: http://www.rassoc.com/gregr/weblog/stories/2002/07/09/webServicesSecurityHttpDigestAuthenticationWithoutActiveDirectory.html) + + // A1 = unq(username-value) ":" unq(realm-value) ":" passwd + $A1 = $username. ':' . (isset($digestRequest['realm']) ? $digestRequest['realm'] : '') . ':' . $password; + + // H(A1) = MD5(A1) + $HA1 = md5($A1); + + // A2 = Method ":" digest-uri-value + $A2 = 'POST:' . $this->digest_uri; + + // H(A2) + $HA2 = md5($A2); + + // KD(secret, data) = H(concat(secret, ":", data)) + // if qop == auth: + // request-digest = <"> < KD ( H(A1), unq(nonce-value) + // ":" nc-value + // ":" unq(cnonce-value) + // ":" unq(qop-value) + // ":" H(A2) + // ) <"> + // if qop is missing, + // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <"> + + $unhashedDigest = ''; + $nonce = isset($digestRequest['nonce']) ? $digestRequest['nonce'] : ''; + $cnonce = $nonce; + if ($digestRequest['qop'] != '') { + $unhashedDigest = $HA1 . ':' . $nonce . ':' . sprintf("%08d", $digestRequest['nc']) . ':' . $cnonce . ':' . $digestRequest['qop'] . ':' . $HA2; + } else { + $unhashedDigest = $HA1 . ':' . $nonce . ':' . $HA2; + } + + $hashedDigest = md5($unhashedDigest); + + $this->outgoing_headers['Authorization'] = 'Digest username="' . $username . '", realm="' . $digestRequest['realm'] . '", nonce="' . $nonce . '", uri="' . $this->digest_uri . '", cnonce="' . $cnonce . '", nc=' . sprintf("%08x", $digestRequest['nc']) . ', qop="' . $digestRequest['qop'] . '", response="' . $hashedDigest . '"'; + } + } elseif ($authtype == 'certificate') { + $this->certRequest = $certRequest; + } + $this->username = $username; + $this->password = $password; + $this->authtype = $authtype; + $this->digestRequest = $digestRequest; + + if (isset($this->outgoing_headers['Authorization'])) { + $this->debug('set Authorization: ' . substr($this->outgoing_headers['Authorization'], 0, 12) . '...'); + } else { + $this->debug('Authorization header not set'); + } + } + + /** + * set the soapaction value + * + * @param string $soapaction + * @access public + */ + function setSOAPAction($soapaction) { + $this->outgoing_headers['SOAPAction'] = '"' . $soapaction . '"'; + $this->debug('set SOAPAction: ' . $this->outgoing_headers['SOAPAction']); + } + + /** + * use http encoding + * + * @param string $enc encoding style. supported values: gzip, deflate, or both + * @access public + */ + function setEncoding($enc='gzip, deflate') { + if (function_exists('gzdeflate')) { + $this->protocol_version = '1.1'; + $this->outgoing_headers['Accept-Encoding'] = $enc; + $this->debug('set Accept-Encoding: ' . $this->outgoing_headers['Accept-Encoding']); + if (!isset($this->outgoing_headers['Connection'])) { + $this->outgoing_headers['Connection'] = 'close'; + $this->persistentConnection = false; + $this->debug('set Connection: ' . $this->outgoing_headers['Connection']); + } + set_magic_quotes_runtime(0); + // deprecated + $this->encoding = $enc; + } + } + + /** + * set proxy info here + * + * @param string $proxyhost + * @param string $proxyport + * @param string $proxyusername + * @param string $proxypassword + * @access public + */ + function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') { + $this->uri = $this->url; + $this->host = $proxyhost; + $this->port = $proxyport; + if ($proxyusername != '' && $proxypassword != '') { + $this->outgoing_headers['Proxy-Authorization'] = ' Basic '.base64_encode($proxyusername.':'.$proxypassword); + $this->debug('set Proxy-Authorization: ' . $this->outgoing_headers['Proxy-Authorization']); + } + } + + /** + * decode a string that is encoded w/ "chunked' transfer encoding + * as defined in RFC2068 19.4.6 + * + * @param string $buffer + * @param string $lb + * @returns string + * @access public + * @deprecated since at least TYPO3 4.3, will be removed in TYPO3 4.5. + */ + function decodeChunked($buffer, $lb){ + t3lib_div::logDeprecatedFunction(); + + // length := 0 + $length = 0; + $new = ''; + + // read chunk-size, chunk-extension (if any) and CRLF + // get the position of the linebreak + $chunkend = strpos($buffer, $lb); + if ($chunkend == FALSE) { + $this->debug('no linebreak found in decodeChunked'); + return $new; + } + $temp = substr($buffer,0,$chunkend); + $chunk_size = hexdec( trim($temp) ); + $chunkstart = $chunkend + strlen($lb); + // while (chunk-size > 0) { + while ($chunk_size > 0) { + $this->debug("chunkstart: $chunkstart chunk_size: $chunk_size"); + $chunkend = strpos( $buffer, $lb, $chunkstart + $chunk_size); + + // Just in case we got a broken connection + if ($chunkend == FALSE) { + $chunk = substr($buffer,$chunkstart); + // append chunk-data to entity-body + $new .= $chunk; + $length += strlen($chunk); + break; + } + + // read chunk-data and CRLF + $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart); + // append chunk-data to entity-body + $new .= $chunk; + // length := length + chunk-size + $length += strlen($chunk); + // read chunk-size and CRLF + $chunkstart = $chunkend + strlen($lb); + + $chunkend = strpos($buffer, $lb, $chunkstart) + strlen($lb); + if ($chunkend == FALSE) { + break; //Just in case we got a broken connection + } + $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart); + $chunk_size = hexdec( trim($temp) ); + $chunkstart = $chunkend; + } + return $new; + } + + /* + * Writes payload, including HTTP headers, to $this->outgoing_payload. + */ + function buildPayload($data, $cookie_str = '') { + // add content-length header + $this->outgoing_headers['Content-Length'] = strlen($data); + $this->debug('set Content-Length: ' . $this->outgoing_headers['Content-Length']); + + // start building outgoing payload: + $req = "$this->request_method $this->uri HTTP/$this->protocol_version"; + $this->debug("HTTP request: $req"); + $this->outgoing_payload = "$req\r\n"; + + // loop thru headers, serializing + foreach($this->outgoing_headers as $k => $v){ + $hdr = $k.': '.$v; + $this->debug("HTTP header: $hdr"); + $this->outgoing_payload .= "$hdr\r\n"; + } + + // add any cookies + if ($cookie_str != '') { + $hdr = 'Cookie: '.$cookie_str; + $this->debug("HTTP header: $hdr"); + $this->outgoing_payload .= "$hdr\r\n"; + } + + // header/body separator + $this->outgoing_payload .= "\r\n"; + + // add data + $this->outgoing_payload .= $data; + } + + function sendRequest($data, $cookies = NULL) { + // build cookie string + $cookie_str = $this->getCookiesForRequest($cookies, (($this->scheme == 'ssl') || ($this->scheme == 'https'))); + + // build payload + $this->buildPayload($data, $cookie_str); + + if ($this->scheme == 'http' || $this->scheme == 'ssl') { + // send payload + if(!fputs($this->fp, $this->outgoing_payload, strlen($this->outgoing_payload))) { + $this->setError('couldn\'t write message data to socket'); + $this->debug('couldn\'t write message data to socket'); + return false; + } + $this->debug('wrote data to socket, length = ' . strlen($this->outgoing_payload)); + return true; + } else if ($this->scheme == 'https') { + // set payload + // TODO: cURL does say this should only be the verb, and in fact it + // turns out that the URI and HTTP version are appended to this, which + // some servers refuse to work with + //curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $this->outgoing_payload); + foreach($this->outgoing_headers as $k => $v){ + $curl_headers[] = "$k: $v"; + } + if ($cookie_str != '') { + $curl_headers[] = 'Cookie: ' . $cookie_str; + } + curl_setopt($this->ch, CURLOPT_HTTPHEADER, $curl_headers); + if ($this->request_method == "POST") { + curl_setopt($this->ch, CURLOPT_POST, 1); + curl_setopt($this->ch, CURLOPT_POSTFIELDS, $data); + } else { + } + $this->debug('set cURL payload'); + return true; + } + } + + function getResponse(){ + $this->incoming_payload = ''; + + if ($this->scheme == 'http' || $this->scheme == 'ssl') { + // loop until headers have been retrieved + $data = ''; + while (!isset($lb)){ + + // We might EOF during header read. + if(feof($this->fp)) { + $this->incoming_payload = $data; + $this->debug('found no headers before EOF after length ' . strlen($data)); + $this->debug("received before EOF:\n" . $data); + $this->setError('server failed to send headers'); + return false; + } + + $tmp = fgets($this->fp, 256); + $tmplen = strlen($tmp); + $this->debug("read line of $tmplen bytes: " . trim($tmp)); + + if ($tmplen == 0) { + $this->incoming_payload = $data; + $this->debug('socket read of headers timed out after length ' . strlen($data)); + $this->debug("read before timeout: " . $data); + $this->setError('socket read of headers timed out'); + return false; + } + + $data .= $tmp; + $pos = strpos($data,"\r\n\r\n"); + if($pos > 1){ + $lb = "\r\n"; + } else { + $pos = strpos($data,"\n\n"); + if($pos > 1){ + $lb = "\n"; + } + } + // remove 100 header + if(isset($lb) && preg_match('/^HTTP\/1.1 100/',$data)){ + unset($lb); + $data = ''; + }// + } + // store header data + $this->incoming_payload .= $data; + $this->debug('found end of headers after length ' . strlen($data)); + // process headers + $header_data = trim(substr($data,0,$pos)); + $header_array = explode($lb,$header_data); + $this->incoming_headers = array(); + $this->incoming_cookies = array(); + foreach($header_array as $header_line){ + $arr = explode(':',$header_line, 2); + if(count($arr) > 1){ + $header_name = strtolower(trim($arr[0])); + $this->incoming_headers[$header_name] = trim($arr[1]); + if ($header_name == 'set-cookie') { + // TODO: allow multiple cookies from parseCookie + $cookie = $this->parseCookie(trim($arr[1])); + if ($cookie) { + $this->incoming_cookies[] = $cookie; + $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']); + } else { + $this->debug('did not find cookie in ' . trim($arr[1])); + } + } + } else if (isset($header_name)) { + // append continuation line to previous header + $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line; + } + } + + // loop until msg has been received + if (isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked') { + $content_length = 2147483647; // ignore any content-length header + $chunked = true; + $this->debug("want to read chunked content"); + } elseif (isset($this->incoming_headers['content-length'])) { + $content_length = $this->incoming_headers['content-length']; + $chunked = false; + $this->debug("want to read content of length $content_length"); + } else { + $content_length = 2147483647; + $chunked = false; + $this->debug("want to read content to EOF"); + } + $data = ''; + do { + if ($chunked) { + $tmp = fgets($this->fp, 256); + $tmplen = strlen($tmp); + $this->debug("read chunk line of $tmplen bytes"); + if ($tmplen == 0) { + $this->incoming_payload = $data; + $this->debug('socket read of chunk length timed out after length ' . strlen($data)); + $this->debug("read before timeout:\n" . $data); + $this->setError('socket read of chunk length timed out'); + return false; + } + $content_length = hexdec(trim($tmp)); + $this->debug("chunk length $content_length"); + } + $strlen = 0; + while (($strlen < $content_length) && (!feof($this->fp))) { + $readlen = min(8192, $content_length - $strlen); + $tmp = fread($this->fp, $readlen); + $tmplen = strlen($tmp); + $this->debug("read buffer of $tmplen bytes"); + if (($tmplen == 0) && (!feof($this->fp))) { + $this->incoming_payload = $data; + $this->debug('socket read of body timed out after length ' . strlen($data)); + $this->debug("read before timeout:\n" . $data); + $this->setError('socket read of body timed out'); + return false; + } + $strlen += $tmplen; + $data .= $tmp; + } + if ($chunked && ($content_length > 0)) { + $tmp = fgets($this->fp, 256); + $tmplen = strlen($tmp); + $this->debug("read chunk terminator of $tmplen bytes"); + if ($tmplen == 0) { + $this->incoming_payload = $data; + $this->debug('socket read of chunk terminator timed out after length ' . strlen($data)); + $this->debug("read before timeout:\n" . $data); + $this->setError('socket read of chunk terminator timed out'); + return false; + } + } + } while ($chunked && ($content_length > 0) && (!feof($this->fp))); + if (feof($this->fp)) { + $this->debug('read to EOF'); + } + $this->debug('read body of length ' . strlen($data)); + $this->incoming_payload .= $data; + $this->debug('received a total of '.strlen($this->incoming_payload).' bytes of data from server'); + + // close filepointer + if( + (isset($this->incoming_headers['connection']) && strtolower($this->incoming_headers['connection']) == 'close') || + (! $this->persistentConnection) || feof($this->fp)){ + fclose($this->fp); + $this->fp = false; + $this->debug('closed socket'); + } + + // connection was closed unexpectedly + if($this->incoming_payload == ''){ + $this->setError('no response from server'); + return false; + } + + // decode transfer-encoding +// if(isset($this->incoming_headers['transfer-encoding']) && strtolower($this->incoming_headers['transfer-encoding']) == 'chunked'){ +// if(!$data = $this->decodeChunked($data, $lb)){ +// $this->setError('Decoding of chunked data failed'); +// return false; +// } + //print "
\nde-chunked:\n---------------\n$data\n\n---------------\n
"; + // set decoded payload +// $this->incoming_payload = $header_data.$lb.$lb.$data; +// } + + } else if ($this->scheme == 'https') { + // send and receive + $this->debug('send and receive with cURL'); + $this->incoming_payload = curl_exec($this->ch); + $data = $this->incoming_payload; + + $cErr = curl_error($this->ch); + if ($cErr != '') { + $err = 'cURL ERROR: '.curl_errno($this->ch).': '.$cErr.'
'; + // TODO: there is a PHP bug that can cause this to SEGV for CURLINFO_CONTENT_TYPE + foreach(curl_getinfo($this->ch) as $k => $v){ + $err .= "$k: $v
"; + } + $this->debug($err); + $this->setError($err); + curl_close($this->ch); + return false; + } else { + //echo '
';
+			//var_dump(curl_getinfo($this->ch));
+			//echo '
'; + } + // close curl + $this->debug('No cURL error, closing cURL'); + curl_close($this->ch); + + // remove 100 header(s) + while (preg_match('/^HTTP\/1.1 100/',$data)) { + if ($pos = strpos($data,"\r\n\r\n")) { + $data = ltrim(substr($data,$pos)); + } elseif($pos = strpos($data,"\n\n") ) { + $data = ltrim(substr($data,$pos)); + } + } + + // separate content from HTTP headers + if ($pos = strpos($data,"\r\n\r\n")) { + $lb = "\r\n"; + } elseif( $pos = strpos($data,"\n\n")) { + $lb = "\n"; + } else { + $this->debug('no proper separation of headers and document'); + $this->setError('no proper separation of headers and document'); + return false; + } + $header_data = trim(substr($data,0,$pos)); + $header_array = explode($lb,$header_data); + $data = ltrim(substr($data,$pos)); + $this->debug('found proper separation of headers and document'); + $this->debug('cleaned data, stringlen: '.strlen($data)); + // clean headers + foreach ($header_array as $header_line) { + $arr = explode(':',$header_line,2); + if(count($arr) > 1){ + $header_name = strtolower(trim($arr[0])); + $this->incoming_headers[$header_name] = trim($arr[1]); + if ($header_name == 'set-cookie') { + // TODO: allow multiple cookies from parseCookie + $cookie = $this->parseCookie(trim($arr[1])); + if ($cookie) { + $this->incoming_cookies[] = $cookie; + $this->debug('found cookie: ' . $cookie['name'] . ' = ' . $cookie['value']); + } else { + $this->debug('did not find cookie in ' . trim($arr[1])); + } + } + } else if (isset($header_name)) { + // append continuation line to previous header + $this->incoming_headers[$header_name] .= $lb . ' ' . $header_line; + } + } + } + + $arr = explode(' ', $header_array[0], 3); + $http_version = $arr[0]; + $http_status = intval($arr[1]); + $http_reason = count($arr) > 2 ? $arr[2] : ''; + + // see if we need to resend the request with http digest authentication + if (isset($this->incoming_headers['location']) && $http_status == 301) { + $this->debug("Got 301 $http_reason with Location: " . $this->incoming_headers['location']); + $this->setURL($this->incoming_headers['location']); + $this->tryagain = true; + return false; + } + + // see if we need to resend the request with http digest authentication + if (isset($this->incoming_headers['www-authenticate']) && $http_status == 401) { + $this->debug("Got 401 $http_reason with WWW-Authenticate: " . $this->incoming_headers['www-authenticate']); + if (strstr($this->incoming_headers['www-authenticate'], "Digest ")) { + $this->debug('Server wants digest authentication'); + // remove "Digest " from our elements + $digestString = str_replace('Digest ', '', $this->incoming_headers['www-authenticate']); + + // parse elements into array + $digestElements = explode(',', $digestString); + foreach ($digestElements as $val) { + $tempElement = explode('=', trim($val), 2); + $digestRequest[$tempElement[0]] = str_replace("\"", '', $tempElement[1]); + } + + // should have (at least) qop, realm, nonce + if (isset($digestRequest['nonce'])) { + $this->setCredentials($this->username, $this->password, 'digest', $digestRequest); + $this->tryagain = true; + return false; + } + } + $this->debug('HTTP authentication failed'); + $this->setError('HTTP authentication failed'); + return false; + } + + if ( + ($http_status >= 300 && $http_status <= 307) || + ($http_status >= 400 && $http_status <= 417) || + ($http_status >= 501 && $http_status <= 505) + ) { + $this->setError("Unsupported HTTP response status $http_status $http_reason (soapclient->response has contents of the response)"); + return false; + } + + // decode content-encoding + if(isset($this->incoming_headers['content-encoding']) && $this->incoming_headers['content-encoding'] != ''){ + if(strtolower($this->incoming_headers['content-encoding']) == 'deflate' || strtolower($this->incoming_headers['content-encoding']) == 'gzip'){ + // if decoding works, use it. else assume data wasn't gzencoded + if(function_exists('gzinflate')){ + //$timer->setMarker('starting decoding of gzip/deflated content'); + // IIS 5 requires gzinflate instead of gzuncompress (similar to IE 5 and gzdeflate v. gzcompress) + // this means there are no Zlib headers, although there should be + $this->debug('The gzinflate function exists'); + $datalen = strlen($data); + if ($this->incoming_headers['content-encoding'] == 'deflate') { + if ($degzdata = @gzinflate($data)) { + $data = $degzdata; + $this->debug('The payload has been inflated to ' . strlen($data) . ' bytes'); + if (strlen($data) < $datalen) { + // test for the case that the payload has been compressed twice + $this->debug('The inflated payload is smaller than the gzipped one; try again'); + if ($degzdata = @gzinflate($data)) { + $data = $degzdata; + $this->debug('The payload has been inflated again to ' . strlen($data) . ' bytes'); + } + } + } else { + $this->debug('Error using gzinflate to inflate the payload'); + $this->setError('Error using gzinflate to inflate the payload'); + } + } elseif ($this->incoming_headers['content-encoding'] == 'gzip') { + if ($degzdata = @gzinflate(substr($data, 10))) { // do our best + $data = $degzdata; + $this->debug('The payload has been un-gzipped to ' . strlen($data) . ' bytes'); + if (strlen($data) < $datalen) { + // test for the case that the payload has been compressed twice + $this->debug('The un-gzipped payload is smaller than the gzipped one; try again'); + if ($degzdata = @gzinflate(substr($data, 10))) { + $data = $degzdata; + $this->debug('The payload has been un-gzipped again to ' . strlen($data) . ' bytes'); + } + } + } else { + $this->debug('Error using gzinflate to un-gzip the payload'); + $this->setError('Error using gzinflate to un-gzip the payload'); + } + } + //$timer->setMarker('finished decoding of gzip/deflated content'); + //print "\nde-inflated:\n---------------\n$data\n-------------\n"; + // set decoded payload + $this->incoming_payload = $header_data.$lb.$lb.$data; + } else { + $this->debug('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.'); + $this->setError('The server sent compressed data. Your php install must have the Zlib extension compiled in to support this.'); + } + } else { + $this->debug('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']); + $this->setError('Unsupported Content-Encoding ' . $this->incoming_headers['content-encoding']); + } + } else { + $this->debug('No Content-Encoding header'); + } + + if(strlen($data) == 0){ + $this->debug('no data after headers!'); + $this->setError('no data present after HTTP headers'); + return false; + } + + return $data; + } + + function setContentType($type, $charset = false) { + $this->outgoing_headers['Content-Type'] = $type . ($charset ? '; charset=' . $charset : ''); + $this->debug('set Content-Type: ' . $this->outgoing_headers['Content-Type']); + } + + function usePersistentConnection(){ + if (isset($this->outgoing_headers['Accept-Encoding'])) { + return false; + } + $this->protocol_version = '1.1'; + $this->persistentConnection = true; + $this->outgoing_headers['Connection'] = 'Keep-Alive'; + $this->debug('set Connection: ' . $this->outgoing_headers['Connection']); + return true; + } + + /** + * parse an incoming Cookie into it's parts + * + * @param string $cookie_str content of cookie + * @return array with data of that cookie + * @access private + */ + /* + * TODO: allow a Set-Cookie string to be parsed into multiple cookies + */ + function parseCookie($cookie_str) { + $cookie_str = str_replace('; ', ';', $cookie_str) . ';'; + $data = explode(';', $cookie_str); + $value_str = $data[0]; + + $cookie_param = 'domain='; + $start = strpos($cookie_str, $cookie_param); + if ($start > 0) { + $domain = substr($cookie_str, $start + strlen($cookie_param)); + $domain = substr($domain, 0, strpos($domain, ';')); + } else { + $domain = ''; + } + + $cookie_param = 'expires='; + $start = strpos($cookie_str, $cookie_param); + if ($start > 0) { + $expires = substr($cookie_str, $start + strlen($cookie_param)); + $expires = substr($expires, 0, strpos($expires, ';')); + } else { + $expires = ''; + } + + $cookie_param = 'path='; + $start = strpos($cookie_str, $cookie_param); + if ( $start > 0 ) { + $path = substr($cookie_str, $start + strlen($cookie_param)); + $path = substr($path, 0, strpos($path, ';')); + } else { + $path = '/'; + } + + $cookie_param = ';secure;'; + if (strpos($cookie_str, $cookie_param) !== FALSE) { + $secure = true; + } else { + $secure = false; + } + + $sep_pos = strpos($value_str, '='); + + if ($sep_pos) { + $name = substr($value_str, 0, $sep_pos); + $value = substr($value_str, $sep_pos + 1); + $cookie= array( 'name' => $name, + 'value' => $value, + 'domain' => $domain, + 'path' => $path, + 'expires' => $expires, + 'secure' => $secure + ); + return $cookie; + } + return false; + } + + /** + * sort out cookies for the current request + * + * @param array $cookies array with all cookies + * @param boolean $secure is the send-content secure or not? + * @return string for Cookie-HTTP-Header + * @access private + */ + function getCookiesForRequest($cookies, $secure=false) { + $cookie_str = ''; + if ((! is_null($cookies)) && (is_array($cookies))) { + foreach ($cookies as $cookie) { + if (! is_array($cookie)) { + continue; + } + $this->debug("check cookie for validity: ".$cookie['name'].'='.$cookie['value']); + if ((isset($cookie['expires'])) && (! empty($cookie['expires']))) { + if (strtotime($cookie['expires']) <= time()) { + $this->debug('cookie has expired'); + continue; + } + } + if ((isset($cookie['domain'])) && (! empty($cookie['domain']))) { + $domain = preg_quote($cookie['domain']); + if (! preg_match("'.*$domain$'i", $this->host)) { + $this->debug('cookie has different domain'); + continue; + } + } + if ((isset($cookie['path'])) && (! empty($cookie['path']))) { + $path = preg_quote($cookie['path']); + if (! preg_match("'^$path.*'i", $this->path)) { + $this->debug('cookie is for a different path'); + continue; + } + } + if ((! $secure) && (isset($cookie['secure'])) && ($cookie['secure'])) { + $this->debug('cookie is secure, transport is not'); + continue; + } + $cookie_str .= $cookie['name'] . '=' . $cookie['value'] . '; '; + $this->debug('add cookie to Cookie-String: ' . $cookie['name'] . '=' . $cookie['value']); + } + } + return $cookie_str; + } +} + + + +/** +* parses a WSDL file, allows access to it's data, other utility methods +* +* @author Dietrich Ayala +* @version $Id: class.nusoap.php 6339 2009-11-05 21:21:54Z masi $ +* @access public +*/ +class wsdl extends nusoap_base { + // URL or filename of the root of this WSDL + var $wsdl; + // define internal arrays of bindings, ports, operations, messages, etc. + var $schemas = array(); + var $currentSchema; + var $message = array(); + var $complexTypes = array(); + var $messages = array(); + var $currentMessage; + var $currentOperation; + var $portTypes = array(); + var $currentPortType; + var $bindings = array(); + var $currentBinding; + var $ports = array(); + var $currentPort; + var $opData = array(); + var $status = ''; + var $documentation = false; + var $endpoint = ''; + // array of wsdl docs to import + var $import = array(); + // parser vars + var $parser; + var $position = 0; + var $depth = 0; + var $depth_array = array(); + // for getting wsdl + var $proxyhost = ''; + var $proxyport = ''; + var $proxyusername = ''; + var $proxypassword = ''; + var $timeout = 0; + var $response_timeout = 30; + + /** + * constructor + * + * @param string $wsdl WSDL document URL + * @param string $proxyhost + * @param string $proxyport + * @param string $proxyusername + * @param string $proxypassword + * @param integer $timeout set the connection timeout + * @param integer $response_timeout set the response timeout + * @access public + */ + function wsdl($wsdl = '',$proxyhost=false,$proxyport=false,$proxyusername=false,$proxypassword=false,$timeout=0,$response_timeout=30){ + parent::nusoap_base(); + $this->wsdl = $wsdl; + $this->proxyhost = $proxyhost; + $this->proxyport = $proxyport; + $this->proxyusername = $proxyusername; + $this->proxypassword = $proxypassword; + $this->timeout = $timeout; + $this->response_timeout = $response_timeout; + + // parse wsdl file + if ($wsdl != "") { + $this->debug('initial wsdl URL: ' . $wsdl); + $this->parseWSDL($wsdl); + } + // imports + // TODO: handle imports more properly, grabbing them in-line and nesting them + $imported_urls = array(); + $imported = 1; + while ($imported > 0) { + $imported = 0; + // Schema imports + foreach ($this->schemas as $ns => $list) { + foreach ($list as $xs) { + $wsdlparts = parse_url($this->wsdl); // this is bogusly simple! + foreach ($xs->imports as $ns2 => $list2) { + for ($ii = 0; $ii < count($list2); $ii++) { + if (! $list2[$ii]['loaded']) { + $this->schemas[$ns]->imports[$ns2][$ii]['loaded'] = true; + $url = $list2[$ii]['location']; + if ($url != '') { + $urlparts = parse_url($url); + if (!isset($urlparts['host'])) { + $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] . (isset($wsdlparts['port']) ? ':' .$wsdlparts['port'] : '') . + substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path']; + } + if (! in_array($url, $imported_urls)) { + $this->parseWSDL($url); + $imported++; + $imported_urls[] = $url; + } + } else { + $this->debug("Unexpected scenario: empty URL for unloaded import"); + } + } + } + } + } + } + // WSDL imports + $wsdlparts = parse_url($this->wsdl); // this is bogusly simple! + foreach ($this->import as $ns => $list) { + for ($ii = 0; $ii < count($list); $ii++) { + if (! $list[$ii]['loaded']) { + $this->import[$ns][$ii]['loaded'] = true; + $url = $list[$ii]['location']; + if ($url != '') { + $urlparts = parse_url($url); + if (!isset($urlparts['host'])) { + $url = $wsdlparts['scheme'] . '://' . $wsdlparts['host'] . (isset($wsdlparts['port']) ? ':' . $wsdlparts['port'] : '') . + substr($wsdlparts['path'],0,strrpos($wsdlparts['path'],'/') + 1) .$urlparts['path']; + } + if (! in_array($url, $imported_urls)) { + $this->parseWSDL($url); + $imported++; + $imported_urls[] = $url; + } + } else { + $this->debug("Unexpected scenario: empty URL for unloaded import"); + } + } + } + } + } + // add new data to operation data + foreach($this->bindings as $binding => $bindingData) { + if (isset($bindingData['operations']) && is_array($bindingData['operations'])) { + foreach($bindingData['operations'] as $operation => $data) { + $this->debug('post-parse data gathering for ' . $operation); + $this->bindings[$binding]['operations'][$operation]['input'] = + isset($this->bindings[$binding]['operations'][$operation]['input']) ? + array_merge($this->bindings[$binding]['operations'][$operation]['input'], $this->portTypes[ $bindingData['portType'] ][$operation]['input']) : + $this->portTypes[ $bindingData['portType'] ][$operation]['input']; + $this->bindings[$binding]['operations'][$operation]['output'] = + isset($this->bindings[$binding]['operations'][$operation]['output']) ? + array_merge($this->bindings[$binding]['operations'][$operation]['output'], $this->portTypes[ $bindingData['portType'] ][$operation]['output']) : + $this->portTypes[ $bindingData['portType'] ][$operation]['output']; + if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['input']['message'] ])){ + $this->bindings[$binding]['operations'][$operation]['input']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['input']['message'] ]; + } + if(isset($this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ])){ + $this->bindings[$binding]['operations'][$operation]['output']['parts'] = $this->messages[ $this->bindings[$binding]['operations'][$operation]['output']['message'] ]; + } + if (isset($bindingData['style'])) { + $this->bindings[$binding]['operations'][$operation]['style'] = $bindingData['style']; + } + $this->bindings[$binding]['operations'][$operation]['transport'] = isset($bindingData['transport']) ? $bindingData['transport'] : ''; + $this->bindings[$binding]['operations'][$operation]['documentation'] = isset($this->portTypes[ $bindingData['portType'] ][$operation]['documentation']) ? $this->portTypes[ $bindingData['portType'] ][$operation]['documentation'] : ''; + $this->bindings[$binding]['operations'][$operation]['endpoint'] = isset($bindingData['endpoint']) ? $bindingData['endpoint'] : ''; + } + } + } + } + + /** + * parses the wsdl document + * + * @param string $wsdl path or URL + * @access private + */ + function parseWSDL($wsdl = '') + { + if ($wsdl == '') { + $this->debug('no wsdl passed to parseWSDL()!!'); + $this->setError('no wsdl passed to parseWSDL()!!'); + return false; + } + + // parse $wsdl for url format + $wsdl_props = parse_url($wsdl); + + if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'http' || $wsdl_props['scheme'] == 'https')) { + $this->debug('getting WSDL http(s) URL ' . $wsdl); + // get wsdl + $tr = new soap_transport_http($wsdl); + $tr->request_method = 'GET'; + $tr->useSOAPAction = false; + if($this->proxyhost && $this->proxyport){ + $tr->setProxy($this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword); + } + $tr->setEncoding('gzip, deflate'); + $wsdl_string = $tr->send('', $this->timeout, $this->response_timeout); + //$this->debug("WSDL request\n" . $tr->outgoing_payload); + //$this->debug("WSDL response\n" . $tr->incoming_payload); + $this->appendDebug($tr->getDebug()); + // catch errors + if($err = $tr->getError() ){ + $errstr = 'HTTP ERROR: '.$err; + $this->debug($errstr); + $this->setError($errstr); + unset($tr); + return false; + } + unset($tr); + $this->debug("got WSDL URL"); + } else { + // $wsdl is not http(s), so treat it as a file URL or plain file path + if (isset($wsdl_props['scheme']) && ($wsdl_props['scheme'] == 'file') && isset($wsdl_props['path'])) { + $path = isset($wsdl_props['host']) ? ($wsdl_props['host'] . ':' . $wsdl_props['path']) : $wsdl_props['path']; + } else { + $path = $wsdl; + } + $this->debug('getting WSDL file ' . $path); + if ($fp = @fopen($path, 'r')) { + $wsdl_string = ''; + while ($data = fread($fp, 32768)) { + $wsdl_string .= $data; + } + fclose($fp); + } else { + $errstr = "Bad path to WSDL file $path"; + $this->debug($errstr); + $this->setError($errstr); + return false; + } + } + $this->debug('Parse WSDL'); + // end new code added + // Create an XML parser. + $this->parser = xml_parser_create(); + // Set the options for parsing the XML data. + // xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); + xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0); + // Set the object for the parser. + xml_set_object($this->parser, $this); + // Set the element handlers for the parser. + xml_set_element_handler($this->parser, 'start_element', 'end_element'); + xml_set_character_data_handler($this->parser, 'character_data'); + // Parse the XML file. + if (!xml_parse($this->parser, $wsdl_string, true)) { + // Display an error message. + $errstr = sprintf( + 'XML error parsing WSDL from %s on line %d: %s', + $wsdl, + xml_get_current_line_number($this->parser), + xml_error_string(xml_get_error_code($this->parser)) + ); + $this->debug($errstr); + $this->debug("XML payload:\n" . $wsdl_string); + $this->setError($errstr); + return false; + } + // free the parser + xml_parser_free($this->parser); + $this->debug('Parsing WSDL done'); + // catch wsdl parse errors + if($this->getError()){ + return false; + } + return true; + } + + /** + * start-element handler + * + * @param string $parser XML parser object + * @param string $name element name + * @param string $attrs associative array of attributes + * @access private + */ + function start_element($parser, $name, $attrs) + { + if ($this->status == 'schema') { + $this->currentSchema->schemaStartElement($parser, $name, $attrs); + $this->appendDebug($this->currentSchema->getDebug()); + $this->currentSchema->clearDebug(); + } elseif (preg_match('/schema$/', $name)) { + $this->debug('Parsing WSDL schema'); + // $this->debug("startElement for $name ($attrs[name]). status = $this->status (".$this->getLocalPart($name).")"); + $this->status = 'schema'; + $this->currentSchema = new xmlschema('', '', $this->namespaces); + $this->currentSchema->schemaStartElement($parser, $name, $attrs); + $this->appendDebug($this->currentSchema->getDebug()); + $this->currentSchema->clearDebug(); + } else { + // position in the total number of elements, starting from 0 + $pos = $this->position++; + $depth = $this->depth++; + // set self as current value for this depth + $this->depth_array[$depth] = $pos; + $this->message[$pos] = array('cdata' => ''); + // process attributes + if (count($attrs) > 0) { + // register namespace declarations + foreach($attrs as $k => $v) { + if (preg_match('/^xmlns/', $k)) { + if ($ns_prefix = substr(strrchr($k, ':'), 1)) { + $this->namespaces[$ns_prefix] = $v; + } else { + $this->namespaces['ns' . (count($this->namespaces) + 1)] = $v; + } + if ($v == 'http://www.w3.org/2001/XMLSchema' || $v == 'http://www.w3.org/1999/XMLSchema' || $v == 'http://www.w3.org/2000/10/XMLSchema') { + $this->XMLSchemaVersion = $v; + $this->namespaces['xsi'] = $v . '-instance'; + } + } + } + // expand each attribute prefix to its namespace + foreach($attrs as $k => $v) { + $k = strpos($k, ':') ? $this->expandQname($k) : $k; + if ($k != 'location' && $k != 'soapAction' && $k != 'namespace') { + $v = strpos($v, ':') ? $this->expandQname($v) : $v; + } + $eAttrs[$k] = $v; + } + $attrs = $eAttrs; + } else { + $attrs = array(); + } + // get element prefix, namespace and name + if (preg_match('/:/', $name)) { + // get ns prefix + $prefix = substr($name, 0, strpos($name, ':')); + // get ns + $namespace = isset($this->namespaces[$prefix]) ? $this->namespaces[$prefix] : ''; + // get unqualified name + $name = substr(strstr($name, ':'), 1); + } + // process attributes, expanding any prefixes to namespaces + // find status, register data + switch ($this->status) { + case 'message': + if ($name == 'part') { + if (isset($attrs['type'])) { + $this->debug("msg " . $this->currentMessage . ": found part $attrs[name]: " . implode(',', $attrs)); + $this->messages[$this->currentMessage][$attrs['name']] = $attrs['type']; + } + if (isset($attrs['element'])) { + $this->debug("msg " . $this->currentMessage . ": found part $attrs[name]: " . implode(',', $attrs)); + $this->messages[$this->currentMessage][$attrs['name']] = $attrs['element']; + } + } + break; + case 'portType': + switch ($name) { + case 'operation': + $this->currentPortOperation = $attrs['name']; + $this->debug("portType $this->currentPortType operation: $this->currentPortOperation"); + if (isset($attrs['parameterOrder'])) { + $this->portTypes[$this->currentPortType][$attrs['name']]['parameterOrder'] = $attrs['parameterOrder']; + } + break; + case 'documentation': + $this->documentation = true; + break; + // merge input/output data + default: + $m = isset($attrs['message']) ? $this->getLocalPart($attrs['message']) : ''; + $this->portTypes[$this->currentPortType][$this->currentPortOperation][$name]['message'] = $m; + break; + } + break; + case 'binding': + switch ($name) { + case 'binding': + // get ns prefix + if (isset($attrs['style'])) { + $this->bindings[$this->currentBinding]['prefix'] = $prefix; + } + $this->bindings[$this->currentBinding] = array_merge($this->bindings[$this->currentBinding], $attrs); + break; + case 'header': + $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus]['headers'][] = $attrs; + break; + case 'operation': + if (isset($attrs['soapAction'])) { + $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['soapAction'] = $attrs['soapAction']; + } + if (isset($attrs['style'])) { + $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['style'] = $attrs['style']; + } + if (isset($attrs['name'])) { + $this->currentOperation = $attrs['name']; + $this->debug("current binding operation: $this->currentOperation"); + $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['name'] = $attrs['name']; + $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['binding'] = $this->currentBinding; + $this->bindings[$this->currentBinding]['operations'][$this->currentOperation]['endpoint'] = isset($this->bindings[$this->currentBinding]['endpoint']) ? $this->bindings[$this->currentBinding]['endpoint'] : ''; + } + break; + case 'input': + $this->opStatus = 'input'; + break; + case 'output': + $this->opStatus = 'output'; + break; + case 'body': + if (isset($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus])) { + $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = array_merge($this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus], $attrs); + } else { + $this->bindings[$this->currentBinding]['operations'][$this->currentOperation][$this->opStatus] = $attrs; + } + break; + } + break; + case 'service': + switch ($name) { + case 'port': + $this->currentPort = $attrs['name']; + $this->debug('current port: ' . $this->currentPort); + $this->ports[$this->currentPort]['binding'] = $this->getLocalPart($attrs['binding']); + + break; + case 'address': + $this->ports[$this->currentPort]['location'] = $attrs['location']; + $this->ports[$this->currentPort]['bindingType'] = $namespace; + $this->bindings[ $this->ports[$this->currentPort]['binding'] ]['bindingType'] = $namespace; + $this->bindings[ $this->ports[$this->currentPort]['binding'] ]['endpoint'] = $attrs['location']; + break; + } + break; + } + // set status + switch ($name) { + case 'import': + if (isset($attrs['location'])) { + $this->import[$attrs['namespace']][] = array('location' => $attrs['location'], 'loaded' => false); + $this->debug('parsing import ' . $attrs['namespace']. ' - ' . $attrs['location'] . ' (' . count($this->import[$attrs['namespace']]).')'); + } else { + $this->import[$attrs['namespace']][] = array('location' => '', 'loaded' => true); + if (! $this->getPrefixFromNamespace($attrs['namespace'])) { + $this->namespaces['ns'.(count($this->namespaces)+1)] = $attrs['namespace']; + } + $this->debug('parsing import ' . $attrs['namespace']. ' - [no location] (' . count($this->import[$attrs['namespace']]).')'); + } + break; + //wait for schema + //case 'types': + // $this->status = 'schema'; + // break; + case 'message': + $this->status = 'message'; + $this->messages[$attrs['name']] = array(); + $this->currentMessage = $attrs['name']; + break; + case 'portType': + $this->status = 'portType'; + $this->portTypes[$attrs['name']] = array(); + $this->currentPortType = $attrs['name']; + break; + case "binding": + if (isset($attrs['name'])) { + // get binding name + if (strpos($attrs['name'], ':')) { + $this->currentBinding = $this->getLocalPart($attrs['name']); + } else { + $this->currentBinding = $attrs['name']; + } + $this->status = 'binding'; + $this->bindings[$this->currentBinding]['portType'] = $this->getLocalPart($attrs['type']); + $this->debug("current binding: $this->currentBinding of portType: " . $attrs['type']); + } + break; + case 'service': + $this->serviceName = $attrs['name']; + $this->status = 'service'; + $this->debug('current service: ' . $this->serviceName); + break; + case 'definitions': + foreach ($attrs as $name => $value) { + $this->wsdl_info[$name] = $value; + } + break; + } + } + } + + /** + * end-element handler + * + * @param string $parser XML parser object + * @param string $name element name + * @access private + */ + function end_element($parser, $name){ + // unset schema status + if (/*preg_match('/types$/', $name) ||*/ preg_match('/schema$/', $name)) { + $this->status = ""; + $this->appendDebug($this->currentSchema->getDebug()); + $this->currentSchema->clearDebug(); + $this->schemas[$this->currentSchema->schemaTargetNamespace][] = $this->currentSchema; + $this->debug('Parsing WSDL schema done'); + } + if ($this->status == 'schema') { + $this->currentSchema->schemaEndElement($parser, $name); + } else { + // bring depth down a notch + $this->depth--; + } + // end documentation + if ($this->documentation) { + //TODO: track the node to which documentation should be assigned; it can be a part, message, etc. + //$this->portTypes[$this->currentPortType][$this->currentPortOperation]['documentation'] = $this->documentation; + $this->documentation = false; + } + } + + /** + * element content handler + * + * @param string $parser XML parser object + * @param string $data element content + * @access private + */ + function character_data($parser, $data) + { + $pos = isset($this->depth_array[$this->depth]) ? $this->depth_array[$this->depth] : 0; + if (isset($this->message[$pos]['cdata'])) { + $this->message[$pos]['cdata'] .= $data; + } + if ($this->documentation) { + $this->documentation .= $data; + } + } + + function getBindingData($binding) + { + if (is_array($this->bindings[$binding])) { + return $this->bindings[$binding]; + } + } + + /** + * returns an assoc array of operation names => operation data + * + * @param string $bindingType eg: soap, smtp, dime (only soap is currently supported) + * @return array + * @access public + */ + function getOperations($bindingType = 'soap') + { + $ops = array(); + if ($bindingType == 'soap') { + $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/'; + } + // loop thru ports + foreach($this->ports as $port => $portData) { + // binding type of port matches parameter + if ($portData['bindingType'] == $bindingType) { + //$this->debug("getOperations for port $port"); + //$this->debug("port data: " . $this->varDump($portData)); + //$this->debug("bindings: " . $this->varDump($this->bindings[ $portData['binding'] ])); + // merge bindings + if (isset($this->bindings[ $portData['binding'] ]['operations'])) { + $ops = array_merge ($ops, $this->bindings[ $portData['binding'] ]['operations']); + } + } + } + return $ops; + } + + /** + * returns an associative array of data necessary for calling an operation + * + * @param string $operation , name of operation + * @param string $bindingType , type of binding eg: soap + * @return array + * @access public + */ + function getOperationData($operation, $bindingType = 'soap') + { + if ($bindingType == 'soap') { + $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/'; + } + // loop thru ports + foreach($this->ports as $port => $portData) { + // binding type of port matches parameter + if ($portData['bindingType'] == $bindingType) { + // get binding + //foreach($this->bindings[ $portData['binding'] ]['operations'] as $bOperation => $opData) { + foreach(array_keys($this->bindings[ $portData['binding'] ]['operations']) as $bOperation) { + // note that we could/should also check the namespace here + if ($operation == $bOperation) { + $opData = $this->bindings[ $portData['binding'] ]['operations'][$operation]; + return $opData; + } + } + } + } + } + + /** + * returns an associative array of data necessary for calling an operation + * + * @param string $soapAction soapAction for operation + * @param string $bindingType type of binding eg: soap + * @return array + * @access public + */ + function getOperationDataForSoapAction($soapAction, $bindingType = 'soap') { + if ($bindingType == 'soap') { + $bindingType = 'http://schemas.xmlsoap.org/wsdl/soap/'; + } + // loop thru ports + foreach($this->ports as $port => $portData) { + // binding type of port matches parameter + if ($portData['bindingType'] == $bindingType) { + // loop through operations for the binding + foreach ($this->bindings[ $portData['binding'] ]['operations'] as $bOperation => $opData) { + if ($opData['soapAction'] == $soapAction) { + return $opData; + } + } + } + } + } + + /** + * returns an array of information about a given type + * returns false if no type exists by the given name + * + * typeDef = array( + * 'elements' => array(), // refs to elements array + * 'restrictionBase' => '', + * 'phpType' => '', + * 'order' => '(sequence|all)', + * 'attrs' => array() // refs to attributes array + * ) + * + * @param $type string the type + * @param $ns string namespace (not prefix) of the type + * @return mixed + * @access public + * @see xmlschema + */ + function getTypeDef($type, $ns) { + $this->debug("in getTypeDef: type=$type, ns=$ns"); + if ((! $ns) && isset($this->namespaces['tns'])) { + $ns = $this->namespaces['tns']; + $this->debug("in getTypeDef: type namespace forced to $ns"); + } + if (isset($this->schemas[$ns])) { + $this->debug("in getTypeDef: have schema for namespace $ns"); + for ($i = 0; $i < count($this->schemas[$ns]); $i++) { + $xs = &$this->schemas[$ns][$i]; + $t = $xs->getTypeDef($type); + $this->appendDebug($xs->getDebug()); + $xs->clearDebug(); + if ($t) { + if (!isset($t['phpType'])) { + // get info for type to tack onto the element + $uqType = substr($t['type'], strrpos($t['type'], ':') + 1); + $ns = substr($t['type'], 0, strrpos($t['type'], ':')); + $etype = $this->getTypeDef($uqType, $ns); + if ($etype) { + $this->debug("found type for [element] $type:"); + $this->debug($this->varDump($etype)); + if (isset($etype['phpType'])) { + $t['phpType'] = $etype['phpType']; + } + if (isset($etype['elements'])) { + $t['elements'] = $etype['elements']; + } + if (isset($etype['attrs'])) { + $t['attrs'] = $etype['attrs']; + } + } + } + return $t; + } + } + } else { + $this->debug("in getTypeDef: do not have schema for namespace $ns"); + } + return false; + } + + /** + * prints html description of services + * + * @access private + */ + function webDescription(){ + global $HTTP_SERVER_VARS; + + if (isset($_SERVER)) { + $PHP_SELF = $_SERVER['PHP_SELF']; + } elseif (isset($HTTP_SERVER_VARS)) { + $PHP_SELF = $HTTP_SERVER_VARS['PHP_SELF']; + } else { + $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available"); + } + + $b = ' + NuSOAP: '.$this->serviceName.' + + + + +
+

+
'.$this->serviceName.'
+ +
'; + return $b; + } + + /** + * serialize the parsed wsdl + * + * @param mixed $debug whether to put debug=1 in endpoint URL + * @return string serialization of WSDL + * @access public + */ + function serialize($debug = 0) + { + $xml = ''; + $xml .= "\nnamespaces as $k => $v) { + $xml .= " xmlns:$k=\"$v\""; + } + // 10.9.02 - add poulter fix for wsdl and tns declarations + if (isset($this->namespaces['wsdl'])) { + $xml .= " xmlns=\"" . $this->namespaces['wsdl'] . "\""; + } + if (isset($this->namespaces['tns'])) { + $xml .= " targetNamespace=\"" . $this->namespaces['tns'] . "\""; + } + $xml .= '>'; + // imports + if (sizeof($this->import) > 0) { + foreach($this->import as $ns => $list) { + foreach ($list as $ii) { + if ($ii['location'] != '') { + $xml .= ''; + } else { + $xml .= ''; + } + } + } + } + // types + if (count($this->schemas)>=1) { + $xml .= "\n"; + foreach ($this->schemas as $ns => $list) { + foreach ($list as $xs) { + $xml .= $xs->serializeSchema(); + } + } + $xml .= ''; + } + // messages + if (count($this->messages) >= 1) { + foreach($this->messages as $msgName => $msgParts) { + $xml .= "\n'; + if(is_array($msgParts)){ + foreach($msgParts as $partName => $partType) { + // print 'serializing '.$partType.', sv: '.$this->XMLSchemaVersion.'
'; + if (strpos($partType, ':')) { + $typePrefix = $this->getPrefixFromNamespace($this->getPrefix($partType)); + } elseif (isset($this->typemap[$this->namespaces['xsd']][$partType])) { + // print 'checking typemap: '.$this->XMLSchemaVersion.'
'; + $typePrefix = 'xsd'; + } else { + foreach($this->typemap as $ns => $types) { + if (isset($types[$partType])) { + $typePrefix = $this->getPrefixFromNamespace($ns); + } + } + if (!isset($typePrefix)) { + die("$partType has no namespace!"); + } + } + $ns = $this->getNamespaceFromPrefix($typePrefix); + $typeDef = $this->getTypeDef($this->getLocalPart($partType), $ns); + if ($typeDef['typeClass'] == 'element') { + $elementortype = 'element'; + } else { + $elementortype = 'type'; + } + $xml .= ''; + } + } + $xml .= '
'; + } + } + // bindings & porttypes + if (count($this->bindings) >= 1) { + $binding_xml = ''; + $portType_xml = ''; + foreach($this->bindings as $bindingName => $attrs) { + $binding_xml .= "\n'; + $binding_xml .= ''; + $portType_xml .= "\n'; + foreach($attrs['operations'] as $opName => $opParts) { + $binding_xml .= ''; + $binding_xml .= ''; + if (isset($opParts['input']['encodingStyle']) && $opParts['input']['encodingStyle'] != '') { + $enc_style = ' encodingStyle="' . $opParts['input']['encodingStyle'] . '"'; + } else { + $enc_style = ''; + } + $binding_xml .= ''; + if (isset($opParts['output']['encodingStyle']) && $opParts['output']['encodingStyle'] != '') { + $enc_style = ' encodingStyle="' . $opParts['output']['encodingStyle'] . '"'; + } else { + $enc_style = ''; + } + $binding_xml .= ''; + $binding_xml .= ''; + $portType_xml .= ''; + } + $portType_xml .= ''; + $portType_xml .= ''; + $portType_xml .= ''; + } + $portType_xml .= ''; + $binding_xml .= ''; + } + $xml .= $portType_xml . $binding_xml; + } + // services + $xml .= "\nserviceName . '">'; + if (count($this->ports) >= 1) { + foreach($this->ports as $pName => $attrs) { + $xml .= ''; + $xml .= ''; + $xml .= ''; + } + } + $xml .= ''; + return $xml . "\n"; + } + + /** + * serialize PHP values according to a WSDL message definition + * + * TODO + * - multi-ref serialization + * - validate PHP values against type definitions, return errors if invalid + * + * @param string $operation operation name + * @param string $direction (input|output) + * @param mixed $parameters parameter value(s) + * @return mixed parameters serialized as XML or false on error (e.g. operation not found) + * @access public + */ + function serializeRPCParameters($operation, $direction, $parameters) + { + $this->debug("in serializeRPCParameters: operation=$operation, direction=$direction, XMLSchemaVersion=$this->XMLSchemaVersion"); + $this->appendDebug('parameters=' . $this->varDump($parameters)); + + if ($direction != 'input' && $direction != 'output') { + $this->debug('The value of the \$direction argument needs to be either "input" or "output"'); + $this->setError('The value of the \$direction argument needs to be either "input" or "output"'); + return false; + } + if (!$opData = $this->getOperationData($operation)) { + $this->debug('Unable to retrieve WSDL data for operation: ' . $operation); + $this->setError('Unable to retrieve WSDL data for operation: ' . $operation); + return false; + } + $this->debug('opData:'); + $this->appendDebug($this->varDump($opData)); + + // Get encoding style for output and set to current + $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/'; + if(($direction == 'input') && isset($opData['output']['encodingStyle']) && ($opData['output']['encodingStyle'] != $encodingStyle)) { + $encodingStyle = $opData['output']['encodingStyle']; + $enc_style = $encodingStyle; + } + + // set input params + $xml = ''; + if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) { + + $use = $opData[$direction]['use']; + $this->debug('have ' . count($opData[$direction]['parts']) . ' part(s) to serialize'); + if (is_array($parameters)) { + $parametersArrayType = $this->isArraySimpleOrStruct($parameters); + $this->debug('have ' . count($parameters) . ' parameter(s) provided as ' . $parametersArrayType . ' to serialize'); + foreach($opData[$direction]['parts'] as $name => $type) { + $this->debug('serializing part "'.$name.'" of type "'.$type.'"'); + // Track encoding style + if (isset($opData[$direction]['encodingStyle']) && $encodingStyle != $opData[$direction]['encodingStyle']) { + $encodingStyle = $opData[$direction]['encodingStyle']; + $enc_style = $encodingStyle; + } else { + $enc_style = false; + } + // NOTE: add error handling here + // if serializeType returns false, then catch global error and fault + if ($parametersArrayType == 'arraySimple') { + $p = array_shift($parameters); + $this->debug('calling serializeType w/indexed param'); + $xml .= $this->serializeType($name, $type, $p, $use, $enc_style); + } elseif (isset($parameters[$name])) { + $this->debug('calling serializeType w/named param'); + $xml .= $this->serializeType($name, $type, $parameters[$name], $use, $enc_style); + } else { + // TODO: only send nillable + $this->debug('calling serializeType w/null param'); + $xml .= $this->serializeType($name, $type, null, $use, $enc_style); + } + } + } else { + $this->debug('no parameters passed.'); + } + } + $this->debug("serializeRPCParameters returning: $xml"); + return $xml; + } + + /** + * serialize a PHP value according to a WSDL message definition + * + * TODO + * - multi-ref serialization + * - validate PHP values against type definitions, return errors if invalid + * + * @param string $ type name + * @param mixed $ param value + * @return mixed new param or false if initial value didn't validate + * @access public + * @deprecated since at least TYPO3 4.3, will be removed in TYPO3 4.5. + */ + function serializeParameters($operation, $direction, $parameters) + { + t3lib_div::logDeprecatedFunction(); + + $this->debug("in serializeParameters: operation=$operation, direction=$direction, XMLSchemaVersion=$this->XMLSchemaVersion"); + $this->appendDebug('parameters=' . $this->varDump($parameters)); + + if ($direction != 'input' && $direction != 'output') { + $this->debug('The value of the \$direction argument needs to be either "input" or "output"'); + $this->setError('The value of the \$direction argument needs to be either "input" or "output"'); + return false; + } + if (!$opData = $this->getOperationData($operation)) { + $this->debug('Unable to retrieve WSDL data for operation: ' . $operation); + $this->setError('Unable to retrieve WSDL data for operation: ' . $operation); + return false; + } + $this->debug('opData:'); + $this->appendDebug($this->varDump($opData)); + + // Get encoding style for output and set to current + $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/'; + if(($direction == 'input') && isset($opData['output']['encodingStyle']) && ($opData['output']['encodingStyle'] != $encodingStyle)) { + $encodingStyle = $opData['output']['encodingStyle']; + $enc_style = $encodingStyle; + } + + // set input params + $xml = ''; + if (isset($opData[$direction]['parts']) && sizeof($opData[$direction]['parts']) > 0) { + + $use = $opData[$direction]['use']; + $this->debug("use=$use"); + $this->debug('got ' . count($opData[$direction]['parts']) . ' part(s)'); + if (is_array($parameters)) { + $parametersArrayType = $this->isArraySimpleOrStruct($parameters); + $this->debug('have ' . $parametersArrayType . ' parameters'); + foreach($opData[$direction]['parts'] as $name => $type) { + $this->debug('serializing part "'.$name.'" of type "'.$type.'"'); + // Track encoding style + if(isset($opData[$direction]['encodingStyle']) && $encodingStyle != $opData[$direction]['encodingStyle']) { + $encodingStyle = $opData[$direction]['encodingStyle']; + $enc_style = $encodingStyle; + } else { + $enc_style = false; + } + // NOTE: add error handling here + // if serializeType returns false, then catch global error and fault + if ($parametersArrayType == 'arraySimple') { + $p = array_shift($parameters); + $this->debug('calling serializeType w/indexed param'); + $xml .= $this->serializeType($name, $type, $p, $use, $enc_style); + } elseif (isset($parameters[$name])) { + $this->debug('calling serializeType w/named param'); + $xml .= $this->serializeType($name, $type, $parameters[$name], $use, $enc_style); + } else { + // TODO: only send nillable + $this->debug('calling serializeType w/null param'); + $xml .= $this->serializeType($name, $type, null, $use, $enc_style); + } + } + } else { + $this->debug('no parameters passed.'); + } + } + $this->debug("serializeParameters returning: $xml"); + return $xml; + } + + /** + * serializes a PHP value according a given type definition + * + * @param string $name name of value (part or element) + * @param string $type XML schema type of value (type or element) + * @param mixed $value a native PHP value (parameter value) + * @param string $use use for part (encoded|literal) + * @param string $encodingStyle SOAP encoding style for the value (if different than the enclosing style) + * @param boolean $unqualified a kludge for what should be XML namespace form handling + * @return string value serialized as an XML string + * @access private + */ + function serializeType($name, $type, $value, $use='encoded', $encodingStyle=false, $unqualified=false) + { + $this->debug("in serializeType: name=$name, type=$type, use=$use, encodingStyle=$encodingStyle, unqualified=" . ($unqualified ? "unqualified" : "qualified")); + $this->appendDebug("value=" . $this->varDump($value)); + if($use == 'encoded' && $encodingStyle) { + $encodingStyle = ' SOAP-ENV:encodingStyle="' . $encodingStyle . '"'; + } + + // if a soapval has been supplied, let its type override the WSDL + if (is_object($value) && get_class($value) == 'soapval') { + if ($value->type_ns) { + $type = $value->type_ns . ':' . $value->type; + $forceType = true; + $this->debug("in serializeType: soapval overrides type to $type"); + } elseif ($value->type) { + $type = $value->type; + $forceType = true; + $this->debug("in serializeType: soapval overrides type to $type"); + } else { + $forceType = false; + $this->debug("in serializeType: soapval does not override type"); + } + $attrs = $value->attributes; + $value = $value->value; + $this->debug("in serializeType: soapval overrides value to $value"); + if ($attrs) { + if (!is_array($value)) { + $value['!'] = $value; + } + foreach ($attrs as $n => $v) { + $value['!' . $n] = $v; + } + $this->debug("in serializeType: soapval provides attributes"); + } + } else { + $forceType = false; + } + + $xml = ''; + if (strpos($type, ':')) { + $uqType = substr($type, strrpos($type, ':') + 1); + $ns = substr($type, 0, strrpos($type, ':')); + $this->debug("in serializeType: got a prefixed type: $uqType, $ns"); + if ($this->getNamespaceFromPrefix($ns)) { + $ns = $this->getNamespaceFromPrefix($ns); + $this->debug("in serializeType: expanded prefixed type: $uqType, $ns"); + } + + if($ns == $this->XMLSchemaVersion || $ns == 'http://schemas.xmlsoap.org/soap/encoding/'){ + $this->debug('in serializeType: type namespace indicates XML Schema or SOAP Encoding type'); + if ($unqualified && $use == 'literal') { + $elementNS = " xmlns=\"\""; + } else { + $elementNS = ''; + } + if (is_null($value)) { + if ($use == 'literal') { + // TODO: depends on minOccurs + $xml = "<$name$elementNS/>"; + } else { + // TODO: depends on nillable, which should be checked before calling this method + $xml = "<$name$elementNS xsi:nil=\"true\" xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"/>"; + } + $this->debug("in serializeType: returning: $xml"); + return $xml; + } + if ($uqType == 'boolean') { + if ((is_string($value) && $value == 'false') || (! $value)) { + $value = 'false'; + } else { + $value = 'true'; + } + } + if ($uqType == 'string' && gettype($value) == 'string') { + $value = $this->expandEntities($value); + } + if (($uqType == 'long' || $uqType == 'unsignedLong') && gettype($value) == 'double') { + $value = sprintf("%.0lf", $value); + } + // it's a scalar + // TODO: what about null/nil values? + // check type isn't a custom type extending xmlschema namespace + if (!$this->getTypeDef($uqType, $ns)) { + if ($use == 'literal') { + if ($forceType) { + $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">$value"; + } else { + $xml = "<$name$elementNS>$value"; + } + } else { + $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>$value"; + } + $this->debug("in serializeType: returning: $xml"); + return $xml; + } + $this->debug('custom type extends XML Schema or SOAP Encoding namespace (yuck)'); + } else if ($ns == 'http://xml.apache.org/xml-soap') { + $this->debug('in serializeType: appears to be Apache SOAP type'); + if ($uqType == 'Map') { + $tt_prefix = $this->getPrefixFromNamespace('http://xml.apache.org/xml-soap'); + if (! $tt_prefix) { + $this->debug('in serializeType: Add namespace for Apache SOAP type'); + $tt_prefix = 'ns' . rand(1000, 9999); + $this->namespaces[$tt_prefix] = 'http://xml.apache.org/xml-soap'; + // force this to be added to usedNamespaces + $tt_prefix = $this->getPrefixFromNamespace('http://xml.apache.org/xml-soap'); + } + $contents = ''; + foreach($value as $k => $v) { + $this->debug("serializing map element: key $k, value $v"); + $contents .= ''; + $contents .= $this->serialize_val($k,'key',false,false,false,false,$use); + $contents .= $this->serialize_val($v,'value',false,false,false,false,$use); + $contents .= ''; + } + if ($use == 'literal') { + if ($forceType) { + $xml = "<$name xsi:type=\"" . $tt_prefix . ":$uqType\">$contents"; + } else { + $xml = "<$name>$contents"; + } + } else { + $xml = "<$name xsi:type=\"" . $tt_prefix . ":$uqType\"$encodingStyle>$contents"; + } + $this->debug("in serializeType: returning: $xml"); + return $xml; + } + $this->debug('in serializeType: Apache SOAP type, but only support Map'); + } + } else { + // TODO: should the type be compared to types in XSD, and the namespace + // set to XSD if the type matches? + $this->debug("in serializeType: No namespace for type $type"); + $ns = ''; + $uqType = $type; + } + if(!$typeDef = $this->getTypeDef($uqType, $ns)){ + $this->setError("$type ($uqType) is not a supported type."); + $this->debug("in serializeType: $type ($uqType) is not a supported type."); + return false; + } else { + $this->debug("in serializeType: found typeDef"); + $this->appendDebug('typeDef=' . $this->varDump($typeDef)); + } + $phpType = $typeDef['phpType']; + $this->debug("in serializeType: uqType: $uqType, ns: $ns, phptype: $phpType, arrayType: " . (isset($typeDef['arrayType']) ? $typeDef['arrayType'] : '') ); + // if php type == struct, map value to the element names + if ($phpType == 'struct') { + if (isset($typeDef['typeClass']) && $typeDef['typeClass'] == 'element') { + $elementName = $uqType; + if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) { + $elementNS = " xmlns=\"$ns\""; + } else { + $elementNS = " xmlns=\"\""; + } + } else { + $elementName = $name; + if ($unqualified) { + $elementNS = " xmlns=\"\""; + } else { + $elementNS = ''; + } + } + if (is_null($value)) { + if ($use == 'literal') { + // TODO: depends on minOccurs + $xml = "<$elementName$elementNS/>"; + } else { + $xml = "<$elementName$elementNS xsi:nil=\"true\" xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"/>"; + } + $this->debug("in serializeType: returning: $xml"); + return $xml; + } + if (is_object($value)) { + $value = get_object_vars($value); + } + if (is_array($value)) { + $elementAttrs = $this->serializeComplexTypeAttributes($typeDef, $value, $ns, $uqType); + if ($use == 'literal') { + if ($forceType) { + $xml = "<$elementName$elementNS$elementAttrs xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">"; + } else { + $xml = "<$elementName$elementNS$elementAttrs>"; + } + } else { + $xml = "<$elementName$elementNS$elementAttrs xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>"; + } + + $xml .= $this->serializeComplexTypeElements($typeDef, $value, $ns, $uqType, $use, $encodingStyle); + $xml .= ""; + } else { + $this->debug("in serializeType: phpType is struct, but value is not an array"); + $this->setError("phpType is struct, but value is not an array: see debug output for details"); + $xml = ''; + } + } elseif ($phpType == 'array') { + if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) { + $elementNS = " xmlns=\"$ns\""; + } else { + if ($unqualified) { + $elementNS = " xmlns=\"\""; + } else { + $elementNS = ''; + } + } + if (is_null($value)) { + if ($use == 'literal') { + // TODO: depends on minOccurs + $xml = "<$name$elementNS/>"; + } else { + $xml = "<$name$elementNS xsi:nil=\"true\" xsi:type=\"" . + $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/') . + ":Array\" " . + $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/') . + ':arrayType="' . + $this->getPrefixFromNamespace($this->getPrefix($typeDef['arrayType'])) . + ':' . + $this->getLocalPart($typeDef['arrayType'])."[0]\"/>"; + } + $this->debug("in serializeType: returning: $xml"); + return $xml; + } + if (isset($typeDef['multidimensional'])) { + $nv = array(); + foreach($value as $v) { + $cols = ',' . sizeof($v); + $nv = array_merge($nv, $v); + } + $value = $nv; + } else { + $cols = ''; + } + if (is_array($value) && sizeof($value) >= 1) { + $rows = sizeof($value); + $contents = ''; + foreach($value as $k => $v) { + $this->debug("serializing array element: $k, $v of type: $typeDef[arrayType]"); + //if (strpos($typeDef['arrayType'], ':') ) { + if (!in_array($typeDef['arrayType'],$this->typemap['http://www.w3.org/2001/XMLSchema'])) { + $contents .= $this->serializeType('item', $typeDef['arrayType'], $v, $use); + } else { + $contents .= $this->serialize_val($v, 'item', $typeDef['arrayType'], null, $this->XMLSchemaVersion, false, $use); + } + } + } else { + $rows = 0; + $contents = null; + } + // TODO: for now, an empty value will be serialized as a zero element + // array. Revisit this when coding the handling of null/nil values. + if ($use == 'literal') { + $xml = "<$name$elementNS>" + .$contents + .""; + } else { + $xml = "<$name$elementNS xsi:type=\"".$this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/').':Array" '. + $this->getPrefixFromNamespace('http://schemas.xmlsoap.org/soap/encoding/') + .':arrayType="' + .$this->getPrefixFromNamespace($this->getPrefix($typeDef['arrayType'])) + .":".$this->getLocalPart($typeDef['arrayType'])."[$rows$cols]\">" + .$contents + .""; + } + } elseif ($phpType == 'scalar') { + if (isset($typeDef['form']) && ($typeDef['form'] == 'qualified')) { + $elementNS = " xmlns=\"$ns\""; + } else { + if ($unqualified) { + $elementNS = " xmlns=\"\""; + } else { + $elementNS = ''; + } + } + if ($use == 'literal') { + if ($forceType) { + $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\">$value"; + } else { + $xml = "<$name$elementNS>$value"; + } + } else { + $xml = "<$name$elementNS xsi:type=\"" . $this->getPrefixFromNamespace($ns) . ":$uqType\"$encodingStyle>$value"; + } + } + $this->debug("in serializeType: returning: $xml"); + return $xml; + } + + /** + * serializes the attributes for a complexType + * + * @param array $typeDef our internal representation of an XML schema type (or element) + * @param mixed $value a native PHP value (parameter value) + * @param string $ns the namespace of the type + * @param string $uqType the local part of the type + * @return string value serialized as an XML string + * @access private + */ + function serializeComplexTypeAttributes($typeDef, $value, $ns, $uqType) { + $xml = ''; + if (isset($typeDef['attrs']) && is_array($typeDef['attrs'])) { + $this->debug("serialize attributes for XML Schema type $ns:$uqType"); + if (is_array($value)) { + $xvalue = $value; + } elseif (is_object($value)) { + $xvalue = get_object_vars($value); + } else { + $this->debug("value is neither an array nor an object for XML Schema type $ns:$uqType"); + $xvalue = array(); + } + foreach ($typeDef['attrs'] as $aName => $attrs) { + if (isset($xvalue['!' . $aName])) { + $xname = '!' . $aName; + $this->debug("value provided for attribute $aName with key $xname"); + } elseif (isset($xvalue[$aName])) { + $xname = $aName; + $this->debug("value provided for attribute $aName with key $xname"); + } elseif (isset($attrs['default'])) { + $xname = '!' . $aName; + $xvalue[$xname] = $attrs['default']; + $this->debug('use default value of ' . $xvalue[$aName] . ' for attribute ' . $aName); + } else { + $xname = ''; + $this->debug("no value provided for attribute $aName"); + } + if ($xname) { + $xml .= " $aName=\"" . $this->expandEntities($xvalue[$xname]) . "\""; + } + } + } else { + $this->debug("no attributes to serialize for XML Schema type $ns:$uqType"); + } + if (isset($typeDef['extensionBase'])) { + $ns = $this->getPrefix($typeDef['extensionBase']); + $uqType = $this->getLocalPart($typeDef['extensionBase']); + if ($this->getNamespaceFromPrefix($ns)) { + $ns = $this->getNamespaceFromPrefix($ns); + } + if ($typeDef = $this->getTypeDef($uqType, $ns)) { + $this->debug("serialize attributes for extension base $ns:$uqType"); + $xml .= $this->serializeComplexTypeAttributes($typeDef, $value, $ns, $uqType); + } else { + $this->debug("extension base $ns:$uqType is not a supported type"); + } + } + return $xml; + } + + /** + * serializes the elements for a complexType + * + * @param array $typeDef our internal representation of an XML schema type (or element) + * @param mixed $value a native PHP value (parameter value) + * @param string $ns the namespace of the type + * @param string $uqType the local part of the type + * @param string $use use for part (encoded|literal) + * @param string $encodingStyle SOAP encoding style for the value (if different than the enclosing style) + * @return string value serialized as an XML string + * @access private + */ + function serializeComplexTypeElements($typeDef, $value, $ns, $uqType, $use='encoded', $encodingStyle=false) { + $xml = ''; + if (isset($typeDef['elements']) && is_array($typeDef['elements'])) { + $this->debug("in serializeComplexTypeElements, serialize elements for XML Schema type $ns:$uqType"); + if (is_array($value)) { + $xvalue = $value; + } elseif (is_object($value)) { + $xvalue = get_object_vars($value); + } else { + $this->debug("value is neither an array nor an object for XML Schema type $ns:$uqType"); + $xvalue = array(); + } + // toggle whether all elements are present - ideally should validate against schema + if (count($typeDef['elements']) != count($xvalue)){ + $optionals = true; + } + foreach ($typeDef['elements'] as $eName => $attrs) { + if (!isset($xvalue[$eName])) { + if (isset($attrs['default'])) { + $xvalue[$eName] = $attrs['default']; + $this->debug('use default value of ' . $xvalue[$eName] . ' for element ' . $eName); + } + } + // if user took advantage of a minOccurs=0, then only serialize named parameters + if (isset($optionals) + && (!isset($xvalue[$eName])) + && ( (!isset($attrs['nillable'])) || $attrs['nillable'] != 'true') + ){ + if (isset($attrs['minOccurs']) && $attrs['minOccurs'] <> '0') { + $this->debug("apparent error: no value provided for element $eName with minOccurs=" . $attrs['minOccurs']); + } + // do nothing + $this->debug("no value provided for complexType element $eName and element is not nillable, so serialize nothing"); + } else { + // get value + if (isset($xvalue[$eName])) { + $v = $xvalue[$eName]; + } else { + $v = null; + } + if (isset($attrs['form'])) { + $unqualified = ($attrs['form'] == 'unqualified'); + } else { + $unqualified = false; + } + if (isset($attrs['maxOccurs']) && ($attrs['maxOccurs'] == 'unbounded' || $attrs['maxOccurs'] > 1) && isset($v) && is_array($v) && $this->isArraySimpleOrStruct($v) == 'arraySimple') { + $vv = $v; + foreach ($vv as $k => $v) { + if (isset($attrs['type']) || isset($attrs['ref'])) { + // serialize schema-defined type + $xml .= $this->serializeType($eName, isset($attrs['type']) ? $attrs['type'] : $attrs['ref'], $v, $use, $encodingStyle, $unqualified); + } else { + // serialize generic type (can this ever really happen?) + $this->debug("calling serialize_val() for $v, $eName, false, false, false, false, $use"); + $xml .= $this->serialize_val($v, $eName, false, false, false, false, $use); + } + } + } else { + if (isset($attrs['type']) || isset($attrs['ref'])) { + // serialize schema-defined type + $xml .= $this->serializeType($eName, isset($attrs['type']) ? $attrs['type'] : $attrs['ref'], $v, $use, $encodingStyle, $unqualified); + } else { + // serialize generic type (can this ever really happen?) + $this->debug("calling serialize_val() for $v, $eName, false, false, false, false, $use"); + $xml .= $this->serialize_val($v, $eName, false, false, false, false, $use); + } + } + } + } + } else { + $this->debug("no elements to serialize for XML Schema type $ns:$uqType"); + } + if (isset($typeDef['extensionBase'])) { + $ns = $this->getPrefix($typeDef['extensionBase']); + $uqType = $this->getLocalPart($typeDef['extensionBase']); + if ($this->getNamespaceFromPrefix($ns)) { + $ns = $this->getNamespaceFromPrefix($ns); + } + if ($typeDef = $this->getTypeDef($uqType, $ns)) { + $this->debug("serialize elements for extension base $ns:$uqType"); + $xml .= $this->serializeComplexTypeElements($typeDef, $value, $ns, $uqType, $use, $encodingStyle); + } else { + $this->debug("extension base $ns:$uqType is not a supported type"); + } + } + return $xml; + } + + /** + * adds an XML Schema complex type to the WSDL types + * + * @param string name + * @param string typeClass (complexType|simpleType|attribute) + * @param string phpType: currently supported are array and struct (php assoc array) + * @param string compositor (all|sequence|choice) + * @param string restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array) + * @param array elements = array ( name => array(name=>'',type=>'') ) + * @param array attrs = array(array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'xsd:string[]')) + * @param string arrayType: namespace:name (xsd:string) + * @see xmlschema + * @access public + */ + function addComplexType($name,$typeClass='complexType',$phpType='array',$compositor='',$restrictionBase='',$elements=array(),$attrs=array(),$arrayType='') { + if (count($elements) > 0) { + foreach($elements as $n => $e){ + // expand each element + foreach ($e as $k => $v) { + $k = strpos($k,':') ? $this->expandQname($k) : $k; + $v = strpos($v,':') ? $this->expandQname($v) : $v; + $ee[$k] = $v; + } + $eElements[$n] = $ee; + } + $elements = $eElements; + } + + if (count($attrs) > 0) { + foreach($attrs as $n => $a){ + // expand each attribute + foreach ($a as $k => $v) { + $k = strpos($k,':') ? $this->expandQname($k) : $k; + $v = strpos($v,':') ? $this->expandQname($v) : $v; + $aa[$k] = $v; + } + $eAttrs[$n] = $aa; + } + $attrs = $eAttrs; + } + + $restrictionBase = strpos($restrictionBase,':') ? $this->expandQname($restrictionBase) : $restrictionBase; + $arrayType = strpos($arrayType,':') ? $this->expandQname($arrayType) : $arrayType; + + $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns']; + $this->schemas[$typens][0]->addComplexType($name,$typeClass,$phpType,$compositor,$restrictionBase,$elements,$attrs,$arrayType); + } + + /** + * adds an XML Schema simple type to the WSDL types + * + * @param string $name + * @param string $restrictionBase namespace:name (http://schemas.xmlsoap.org/soap/encoding/:Array) + * @param string $typeClass (should always be simpleType) + * @param string $phpType (should always be scalar) + * @param array $enumeration array of values + * @see xmlschema + * @access public + */ + function addSimpleType($name, $restrictionBase='', $typeClass='simpleType', $phpType='scalar', $enumeration=array()) { + $restrictionBase = strpos($restrictionBase,':') ? $this->expandQname($restrictionBase) : $restrictionBase; + + $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns']; + $this->schemas[$typens][0]->addSimpleType($name, $restrictionBase, $typeClass, $phpType, $enumeration); + } + + /** + * adds an element to the WSDL types + * + * @param array $attrs attributes that must include name and type + * @see xmlschema + * @access public + */ + function addElement($attrs) { + $typens = isset($this->namespaces['types']) ? $this->namespaces['types'] : $this->namespaces['tns']; + $this->schemas[$typens][0]->addElement($attrs); + } + + /** + * register an operation with the server + * + * @param string $name operation (method) name + * @param array $in assoc array of input values: key = param name, value = param type + * @param array $out assoc array of output values: key = param name, value = param type + * @param string $namespace optional The namespace for the operation + * @param string $soapaction optional The soapaction for the operation + * @param string $style (rpc|document) optional The style for the operation Note: when 'document' is specified, parameter and return wrappers are created for you automatically + * @param string $use (encoded|literal) optional The use for the parameters (cannot mix right now) + * @param string $documentation optional The description to include in the WSDL + * @param string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded) + * @access public + */ + function addOperation($name, $in = false, $out = false, $namespace = false, $soapaction = false, $style = 'rpc', $use = 'encoded', $documentation = '', $encodingStyle = ''){ + if ($use == 'encoded' && $encodingStyle == '') { + $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/'; + } + + if ($style == 'document') { + $elements = array(); + foreach ($in as $n => $t) { + $elements[$n] = array('name' => $n, 'type' => $t); + } + $this->addComplexType($name . 'RequestType', 'complexType', 'struct', 'all', '', $elements); + $this->addElement(array('name' => $name, 'type' => $name . 'RequestType')); + $in = array('parameters' => 'tns:' . $name); + + $elements = array(); + foreach ($out as $n => $t) { + $elements[$n] = array('name' => $n, 'type' => $t); + } + $this->addComplexType($name . 'ResponseType', 'complexType', 'struct', 'all', '', $elements); + $this->addElement(array('name' => $name . 'Response', 'type' => $name . 'ResponseType')); + $out = array('parameters' => 'tns:' . $name . 'Response'); + } + + // get binding + $this->bindings[ $this->serviceName . 'Binding' ]['operations'][$name] = + array( + 'name' => $name, + 'binding' => $this->serviceName . 'Binding', + 'endpoint' => $this->endpoint, + 'soapAction' => $soapaction, + 'style' => $style, + 'input' => array( + 'use' => $use, + 'namespace' => $namespace, + 'encodingStyle' => $encodingStyle, + 'message' => $name . 'Request', + 'parts' => $in), + 'output' => array( + 'use' => $use, + 'namespace' => $namespace, + 'encodingStyle' => $encodingStyle, + 'message' => $name . 'Response', + 'parts' => $out), + 'namespace' => $namespace, + 'transport' => 'http://schemas.xmlsoap.org/soap/http', + 'documentation' => $documentation); + // add portTypes + // add messages + if($in) + { + foreach($in as $pName => $pType) + { + if(strpos($pType,':')) { + $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType); + } + $this->messages[$name.'Request'][$pName] = $pType; + } + } else { + $this->messages[$name.'Request']= '0'; + } + if($out) + { + foreach($out as $pName => $pType) + { + if(strpos($pType,':')) { + $pType = $this->getNamespaceFromPrefix($this->getPrefix($pType)).":".$this->getLocalPart($pType); + } + $this->messages[$name.'Response'][$pName] = $pType; + } + } else { + $this->messages[$name.'Response']= '0'; + } + return true; + } +} + + + +/** +* +* soap_parser class parses SOAP XML messages into native PHP values +* +* @author Dietrich Ayala +* @version $Id: class.nusoap.php 6339 2009-11-05 21:21:54Z masi $ +* @access public +*/ +class soap_parser extends nusoap_base { + + var $xml = ''; + var $xml_encoding = ''; + var $method = ''; + var $root_struct = ''; + var $root_struct_name = ''; + var $root_struct_namespace = ''; + var $root_header = ''; + var $document = ''; // incoming SOAP body (text) + // determines where in the message we are (envelope,header,body,method) + var $status = ''; + var $position = 0; + var $depth = 0; + var $default_namespace = ''; + var $namespaces = array(); + var $message = array(); + var $parent = ''; + var $fault = false; + var $fault_code = ''; + var $fault_str = ''; + var $fault_detail = ''; + var $depth_array = array(); + var $debug_flag = true; + var $soapresponse = NULL; + var $responseHeaders = ''; // incoming SOAP headers (text) + var $body_position = 0; + // for multiref parsing: + // array of id => pos + var $ids = array(); + // array of id => hrefs => pos + var $multirefs = array(); + // toggle for auto-decoding element content + var $decode_utf8 = true; + + /** + * constructor that actually does the parsing + * + * @param string $xml SOAP message + * @param string $encoding character encoding scheme of message + * @param string $method method for which XML is parsed (unused?) + * @param string $decode_utf8 whether to decode UTF-8 to ISO-8859-1 + * @access public + */ + function soap_parser($xml,$encoding='UTF-8',$method='',$decode_utf8=true){ + parent::nusoap_base(); + $this->xml = $xml; + $this->xml_encoding = $encoding; + $this->method = $method; + $this->decode_utf8 = $decode_utf8; + + // Check whether content has been read. + if(!empty($xml)){ + // Check XML encoding + $pos_xml = strpos($xml, '', $pos_xml + 2) - $pos_xml + 1); + if (preg_match("/encoding=[\"']([^\"']*)[\"']/", $xml_decl, $res)) { + $xml_encoding = $res[1]; + if (strtoupper($xml_encoding) != $encoding) { + $err = "Charset from HTTP Content-Type '" . $encoding . "' does not match encoding from XML declaration '" . $xml_encoding . "'"; + $this->debug($err); + if ($encoding != 'ISO-8859-1' || strtoupper($xml_encoding) != 'UTF-8') { + $this->setError($err); + return; + } + // when HTTP says ISO-8859-1 (the default) and XML says UTF-8 (the typical), assume the other endpoint is just sloppy and proceed + } else { + $this->debug('Charset from HTTP Content-Type matches encoding from XML declaration'); + } + } else { + $this->debug('No encoding specified in XML declaration'); + } + } else { + $this->debug('No XML declaration'); + } + $this->debug('Entering soap_parser(), length='.strlen($xml).', encoding='.$encoding); + // Create an XML parser - why not xml_parser_create_ns? + $this->parser = xml_parser_create($this->xml_encoding); + // Set the options for parsing the XML data. + //xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); + xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0); + xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, $this->xml_encoding); + // Set the object for the parser. + xml_set_object($this->parser, $this); + // Set the element handlers for the parser. + xml_set_element_handler($this->parser, 'start_element','end_element'); + xml_set_character_data_handler($this->parser,'character_data'); + + // Parse the XML file. + if(!xml_parse($this->parser,$xml,true)){ + // Display an error message. + $err = sprintf('XML error parsing SOAP payload on line %d: %s', + xml_get_current_line_number($this->parser), + xml_error_string(xml_get_error_code($this->parser))); + $this->debug($err); + $this->debug("XML payload:\n" . $xml); + $this->setError($err); + } else { + $this->debug('parsed successfully, found root struct: '.$this->root_struct.' of name '.$this->root_struct_name); + // get final value + $this->soapresponse = $this->message[$this->root_struct]['result']; + // get header value: no, because this is documented as XML string +// if($this->root_header != '' && isset($this->message[$this->root_header]['result'])){ +// $this->responseHeaders = $this->message[$this->root_header]['result']; +// } + // resolve hrefs/ids + if(sizeof($this->multirefs) > 0){ + foreach($this->multirefs as $id => $hrefs){ + $this->debug('resolving multirefs for id: '.$id); + $idVal = $this->buildVal($this->ids[$id]); + if (is_array($idVal) && isset($idVal['!id'])) { + unset($idVal['!id']); + } + foreach($hrefs as $refPos => $ref){ + $this->debug('resolving href at pos '.$refPos); + $this->multirefs[$id][$refPos] = $idVal; + } + } + } + } + xml_parser_free($this->parser); + } else { + $this->debug('xml was empty, didn\'t parse!'); + $this->setError('xml was empty, didn\'t parse!'); + } + } + + /** + * start-element handler + * + * @param resource $parser XML parser object + * @param string $name element name + * @param array $attrs associative array of attributes + * @access private + */ + function start_element($parser, $name, $attrs) { + // position in a total number of elements, starting from 0 + // update class level pos + $pos = $this->position++; + // and set mine + $this->message[$pos] = array('pos' => $pos,'children'=>'','cdata'=>''); + // depth = how many levels removed from root? + // set mine as current global depth and increment global depth value + $this->message[$pos]['depth'] = $this->depth++; + + // else add self as child to whoever the current parent is + if($pos != 0){ + $this->message[$this->parent]['children'] .= '|'.$pos; + } + // set my parent + $this->message[$pos]['parent'] = $this->parent; + // set self as current parent + $this->parent = $pos; + // set self as current value for this depth + $this->depth_array[$this->depth] = $pos; + // get element prefix + if(strpos($name,':')){ + // get ns prefix + $prefix = substr($name,0,strpos($name,':')); + // get unqualified name + $name = substr(strstr($name,':'),1); + } + // set status + if($name == 'Envelope'){ + $this->status = 'envelope'; + } elseif($name == 'Header'){ + $this->root_header = $pos; + $this->status = 'header'; + } elseif($name == 'Body'){ + $this->status = 'body'; + $this->body_position = $pos; + // set method + } elseif($this->status == 'body' && $pos == ($this->body_position+1)){ + $this->status = 'method'; + $this->root_struct_name = $name; + $this->root_struct = $pos; + $this->message[$pos]['type'] = 'struct'; + $this->debug("found root struct $this->root_struct_name, pos $this->root_struct"); + } + // set my status + $this->message[$pos]['status'] = $this->status; + // set name + $this->message[$pos]['name'] = htmlspecialchars($name); + // set attrs + $this->message[$pos]['attrs'] = $attrs; + + // loop through atts, logging ns and type declarations + $attstr = ''; + foreach($attrs as $key => $value){ + $key_prefix = $this->getPrefix($key); + $key_localpart = $this->getLocalPart($key); + // if ns declarations, add to class level array of valid namespaces + if($key_prefix == 'xmlns'){ + if(preg_match('/^http:\/\/www.w3.org\/[0-9]{4}\/XMLSchema$/',$value)){ + $this->XMLSchemaVersion = $value; + $this->namespaces['xsd'] = $this->XMLSchemaVersion; + $this->namespaces['xsi'] = $this->XMLSchemaVersion.'-instance'; + } + $this->namespaces[$key_localpart] = $value; + // set method namespace + if($name == $this->root_struct_name){ + $this->methodNamespace = $value; + } + // if it's a type declaration, set type + } elseif($key_localpart == 'type'){ + $value_prefix = $this->getPrefix($value); + $value_localpart = $this->getLocalPart($value); + $this->message[$pos]['type'] = $value_localpart; + $this->message[$pos]['typePrefix'] = $value_prefix; + if(isset($this->namespaces[$value_prefix])){ + $this->message[$pos]['type_namespace'] = $this->namespaces[$value_prefix]; + } else if(isset($attrs['xmlns:'.$value_prefix])) { + $this->message[$pos]['type_namespace'] = $attrs['xmlns:'.$value_prefix]; + } + // should do something here with the namespace of specified type? + } elseif($key_localpart == 'arrayType'){ + $this->message[$pos]['type'] = 'array'; + /* do arrayType preg_match here + [1] arrayTypeValue ::= atype asize + [2] atype ::= QName rank* + [3] rank ::= '[' (',')* ']' + [4] asize ::= '[' length~ ']' + [5] length ::= nextDimension* Digit+ + [6] nextDimension ::= Digit+ ',' + */ + $expr = '/([A-Za-z0-9_]+):([A-Za-z]+[A-Za-z0-9_]+)\[([0-9]+),?([0-9]*)\]/'; + if(preg_match($expr,$value,$regs)){ + $this->message[$pos]['typePrefix'] = $regs[1]; + $this->message[$pos]['arrayTypePrefix'] = $regs[1]; + if (isset($this->namespaces[$regs[1]])) { + $this->message[$pos]['arrayTypeNamespace'] = $this->namespaces[$regs[1]]; + } else if (isset($attrs['xmlns:'.$regs[1]])) { + $this->message[$pos]['arrayTypeNamespace'] = $attrs['xmlns:'.$regs[1]]; + } + $this->message[$pos]['arrayType'] = $regs[2]; + $this->message[$pos]['arraySize'] = $regs[3]; + $this->message[$pos]['arrayCols'] = $regs[4]; + } + // specifies nil value (or not) + } elseif ($key_localpart == 'nil'){ + $this->message[$pos]['nil'] = ($value == 'true' || $value == '1'); + // some other attribute + } elseif ($key != 'href' && $key != 'xmlns' && $key_localpart != 'encodingStyle' && $key_localpart != 'root') { + $this->message[$pos]['xattrs']['!' . $key] = $value; + } + + if ($key == 'xmlns') { + $this->default_namespace = $value; + } + // log id + if($key == 'id'){ + $this->ids[$value] = $pos; + } + // root + if($key_localpart == 'root' && $value == 1){ + $this->status = 'method'; + $this->root_struct_name = $name; + $this->root_struct = $pos; + $this->debug("found root struct $this->root_struct_name, pos $pos"); + } + // for doclit + $attstr .= " $key=\"$value\""; + } + // get namespace - must be done after namespace atts are processed + if(isset($prefix)){ + $this->message[$pos]['namespace'] = $this->namespaces[$prefix]; + $this->default_namespace = $this->namespaces[$prefix]; + } else { + $this->message[$pos]['namespace'] = $this->default_namespace; + } + if($this->status == 'header'){ + if ($this->root_header != $pos) { + $this->responseHeaders .= "<" . (isset($prefix) ? $prefix . ':' : '') . "$name$attstr>"; + } + } elseif($this->root_struct_name != ''){ + $this->document .= "<" . (isset($prefix) ? $prefix . ':' : '') . "$name$attstr>"; + } + } + + /** + * end-element handler + * + * @param resource $parser XML parser object + * @param string $name element name + * @access private + */ + function end_element($parser, $name) { + // position of current element is equal to the last value left in depth_array for my depth + $pos = $this->depth_array[$this->depth--]; + + // get element prefix + if(strpos($name,':')){ + // get ns prefix + $prefix = substr($name,0,strpos($name,':')); + // get unqualified name + $name = substr(strstr($name,':'),1); + } + + // build to native type + if(isset($this->body_position) && $pos > $this->body_position){ + // deal w/ multirefs + if(isset($this->message[$pos]['attrs']['href'])){ + // get id + $id = substr($this->message[$pos]['attrs']['href'],1); + // add placeholder to href array + $this->multirefs[$id][$pos] = 'placeholder'; + // add set a reference to it as the result value + $this->message[$pos]['result'] =& $this->multirefs[$id][$pos]; + // build complexType values + } elseif($this->message[$pos]['children'] != ''){ + // if result has already been generated (struct/array) + if(!isset($this->message[$pos]['result'])){ + $this->message[$pos]['result'] = $this->buildVal($pos); + } + // build complexType values of attributes and possibly simpleContent + } elseif (isset($this->message[$pos]['xattrs'])) { + if (isset($this->message[$pos]['nil']) && $this->message[$pos]['nil']) { + $this->message[$pos]['xattrs']['!'] = null; + } elseif (isset($this->message[$pos]['cdata']) && trim($this->message[$pos]['cdata']) != '') { + if (isset($this->message[$pos]['type'])) { + $this->message[$pos]['xattrs']['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : ''); + } else { + $parent = $this->message[$pos]['parent']; + if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) { + $this->message[$pos]['xattrs']['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : ''); + } else { + $this->message[$pos]['xattrs']['!'] = $this->message[$pos]['cdata']; + } + } + } + $this->message[$pos]['result'] = $this->message[$pos]['xattrs']; + // set value of simpleType (or nil complexType) + } else { + //$this->debug('adding data for scalar value '.$this->message[$pos]['name'].' of value '.$this->message[$pos]['cdata']); + if (isset($this->message[$pos]['nil']) && $this->message[$pos]['nil']) { + $this->message[$pos]['xattrs']['!'] = null; + } elseif (isset($this->message[$pos]['type'])) { + $this->message[$pos]['result'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : ''); + } else { + $parent = $this->message[$pos]['parent']; + if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) { + $this->message[$pos]['result'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : ''); + } else { + $this->message[$pos]['result'] = $this->message[$pos]['cdata']; + } + } + + /* add value to parent's result, if parent is struct/array + $parent = $this->message[$pos]['parent']; + if($this->message[$parent]['type'] != 'map'){ + if(strtolower($this->message[$parent]['type']) == 'array'){ + $this->message[$parent]['result'][] = $this->message[$pos]['result']; + } else { + $this->message[$parent]['result'][$this->message[$pos]['name']] = $this->message[$pos]['result']; + } + } + */ + } + } + + // for doclit + if($this->status == 'header'){ + if ($this->root_header != $pos) { + $this->responseHeaders .= ""; + } + } elseif($pos >= $this->root_struct){ + $this->document .= ""; + } + // switch status + if($pos == $this->root_struct){ + $this->status = 'body'; + $this->root_struct_namespace = $this->message[$pos]['namespace']; + } elseif($name == 'Body'){ + $this->status = 'envelope'; + } elseif($name == 'Header'){ + $this->status = 'envelope'; + } elseif($name == 'Envelope'){ + // + } + // set parent back to my parent + $this->parent = $this->message[$pos]['parent']; + } + + /** + * element content handler + * + * @param resource $parser XML parser object + * @param string $data element content + * @access private + */ + function character_data($parser, $data){ + $pos = $this->depth_array[$this->depth]; + if ($this->xml_encoding=='UTF-8'){ + // TODO: add an option to disable this for folks who want + // raw UTF-8 that, e.g., might not map to iso-8859-1 + // TODO: this can also be handled with xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, "ISO-8859-1"); + if($this->decode_utf8){ + $data = utf8_decode($data); + } + } + $this->message[$pos]['cdata'] .= $data; + // for doclit + if($this->status == 'header'){ + $this->responseHeaders .= $data; + } else { + $this->document .= $data; + } + } + + /** + * get the parsed message + * + * @return mixed + * @access public + */ + function get_response(){ + return $this->soapresponse; + } + + /** + * get the parsed headers + * + * @return string XML or empty if no headers + * @access public + */ + function getHeaders(){ + return $this->responseHeaders; + } + + /** + * decodes simple types into PHP variables + * + * @param string $value value to decode + * @param string $type XML type to decode + * @param string $typens XML type namespace to decode + * @return mixed PHP value + * @access private + */ + function decodeSimple($value, $type, $typens) { + // TODO: use the namespace! + if ((!isset($type)) || $type == 'string' || $type == 'long' || $type == 'unsignedLong') { + return (string) $value; + } + if ($type == 'int' || $type == 'integer' || $type == 'short' || $type == 'byte') { + return (int) $value; + } + if ($type == 'float' || $type == 'double' || $type == 'decimal') { + return (double) $value; + } + if ($type == 'boolean') { + if (strtolower($value) == 'false' || strtolower($value) == 'f') { + return false; + } + return (boolean) $value; + } + if ($type == 'base64' || $type == 'base64Binary') { + $this->debug('Decode base64 value'); + return base64_decode($value); + } + // obscure numeric types + if ($type == 'nonPositiveInteger' || $type == 'negativeInteger' + || $type == 'nonNegativeInteger' || $type == 'positiveInteger' + || $type == 'unsignedInt' + || $type == 'unsignedShort' || $type == 'unsignedByte') { + return (int) $value; + } + // bogus: parser treats array with no elements as a simple type + if ($type == 'array') { + return array(); + } + // everything else + return (string) $value; + } + + /** + * builds response structures for compound values (arrays/structs) + * and scalars + * + * @param integer $pos position in node tree + * @return mixed PHP value + * @access private + */ + function buildVal($pos){ + if(!isset($this->message[$pos]['type'])){ + $this->message[$pos]['type'] = ''; + } + $this->debug('in buildVal() for '.$this->message[$pos]['name']."(pos $pos) of type ".$this->message[$pos]['type']); + // if there are children... + if($this->message[$pos]['children'] != ''){ + $this->debug('in buildVal, there are children'); + $children = explode('|',$this->message[$pos]['children']); + array_shift($children); // knock off empty + // md array + if(isset($this->message[$pos]['arrayCols']) && $this->message[$pos]['arrayCols'] != ''){ + $r=0; // rowcount + $c=0; // colcount + foreach($children as $child_pos){ + $this->debug("in buildVal, got an MD array element: $r, $c"); + $params[$r][] = $this->message[$child_pos]['result']; + $c++; + if($c == $this->message[$pos]['arrayCols']){ + $c = 0; + $r++; + } + } + // array + } elseif($this->message[$pos]['type'] == 'array' || $this->message[$pos]['type'] == 'Array'){ + $this->debug('in buildVal, adding array '.$this->message[$pos]['name']); + foreach($children as $child_pos){ + $params[] = &$this->message[$child_pos]['result']; + } + // apache Map type: java hashtable + } elseif($this->message[$pos]['type'] == 'Map' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap'){ + $this->debug('in buildVal, Java Map '.$this->message[$pos]['name']); + foreach($children as $child_pos){ + $kv = explode("|",$this->message[$child_pos]['children']); + $params[$this->message[$kv[1]]['result']] = &$this->message[$kv[2]]['result']; + } + // generic compound type + //} elseif($this->message[$pos]['type'] == 'SOAPStruct' || $this->message[$pos]['type'] == 'struct') { + } else { + // Apache Vector type: treat as an array + $this->debug('in buildVal, adding Java Vector '.$this->message[$pos]['name']); + if ($this->message[$pos]['type'] == 'Vector' && $this->message[$pos]['type_namespace'] == 'http://xml.apache.org/xml-soap') { + $notstruct = 1; + } else { + $notstruct = 0; + } + // + foreach($children as $child_pos){ + if($notstruct){ + $params[] = &$this->message[$child_pos]['result']; + } else { + if (isset($params[$this->message[$child_pos]['name']])) { + // de-serialize repeated element name into an array + if ((!is_array($params[$this->message[$child_pos]['name']])) || (!isset($params[$this->message[$child_pos]['name']][0]))) { + $params[$this->message[$child_pos]['name']] = array($params[$this->message[$child_pos]['name']]); + } + $params[$this->message[$child_pos]['name']][] = &$this->message[$child_pos]['result']; + } else { + $params[$this->message[$child_pos]['name']] = &$this->message[$child_pos]['result']; + } + } + } + } + if (isset($this->message[$pos]['xattrs'])) { + $this->debug('in buildVal, handling attributes'); + foreach ($this->message[$pos]['xattrs'] as $n => $v) { + $params[$n] = $v; + } + } + // handle simpleContent + if (isset($this->message[$pos]['cdata']) && trim($this->message[$pos]['cdata']) != '') { + $this->debug('in buildVal, handling simpleContent'); + if (isset($this->message[$pos]['type'])) { + $params['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : ''); + } else { + $parent = $this->message[$pos]['parent']; + if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) { + $params['!'] = $this->decodeSimple($this->message[$pos]['cdata'], $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : ''); + } else { + $params['!'] = $this->message[$pos]['cdata']; + } + } + } + return is_array($params) ? $params : array(); + } else { + $this->debug('in buildVal, no children, building scalar'); + $cdata = isset($this->message[$pos]['cdata']) ? $this->message[$pos]['cdata'] : ''; + if (isset($this->message[$pos]['type'])) { + return $this->decodeSimple($cdata, $this->message[$pos]['type'], isset($this->message[$pos]['type_namespace']) ? $this->message[$pos]['type_namespace'] : ''); + } + $parent = $this->message[$pos]['parent']; + if (isset($this->message[$parent]['type']) && ($this->message[$parent]['type'] == 'array') && isset($this->message[$parent]['arrayType'])) { + return $this->decodeSimple($cdata, $this->message[$parent]['arrayType'], isset($this->message[$parent]['arrayTypeNamespace']) ? $this->message[$parent]['arrayTypeNamespace'] : ''); + } + return $this->message[$pos]['cdata']; + } + } +} + + + +/** +* +* soapclient higher level class for easy usage. +* +* usage: +* +* // instantiate client with server info +* $soapclient = new soapclient( string path [ ,boolean wsdl] ); +* +* // call method, get results +* echo $soapclient->call( string methodname [ ,array parameters] ); +* +* // bye bye client +* unset($soapclient); +* +* @author Dietrich Ayala +* @version $Id: class.nusoap.php 6339 2009-11-05 21:21:54Z masi $ +* @access public +*/ +class soapclient extends nusoap_base { + + var $username = ''; + var $password = ''; + var $authtype = ''; + var $certRequest = array(); + var $requestHeaders = false; // SOAP headers in request (text) + var $responseHeaders = ''; // SOAP headers from response (incomplete namespace resolution) (text) + var $document = ''; // SOAP body response portion (incomplete namespace resolution) (text) + var $endpoint; + var $forceEndpoint = ''; // overrides WSDL endpoint + var $proxyhost = ''; + var $proxyport = ''; + var $proxyusername = ''; + var $proxypassword = ''; + var $xml_encoding = ''; // character set encoding of incoming (response) messages + var $http_encoding = false; + var $timeout = 0; // HTTP connection timeout + var $response_timeout = 30; // HTTP response timeout + var $endpointType = ''; // soap|wsdl, empty for WSDL initialization error + var $persistentConnection = false; + var $defaultRpcParams = false; // This is no longer used + var $request = ''; // HTTP request + var $response = ''; // HTTP response + var $responseData = ''; // SOAP payload of response + var $cookies = array(); // Cookies from response or for request + var $decode_utf8 = true; // toggles whether the parser decodes element content w/ utf8_decode() + var $operations = array(); // WSDL operations, empty for WSDL initialization error + + /* + * fault related variables + */ + /** + * @var fault + * @access public + */ + var $fault; + /** + * @var faultcode + * @access public + */ + var $faultcode; + /** + * @var faultstring + * @access public + */ + var $faultstring; + /** + * @var faultdetail + * @access public + */ + var $faultdetail; + + /** + * constructor + * + * @param mixed $endpoint SOAP server or WSDL URL (string), or wsdl instance (object) + * @param bool $wsdl optional, set to true if using WSDL + * @param int $portName optional portName in WSDL document + * @param string $proxyhost + * @param string $proxyport + * @param string $proxyusername + * @param string $proxypassword + * @param integer $timeout set the connection timeout + * @param integer $response_timeout set the response timeout + * @access public + */ + function soapclient($endpoint,$wsdl = false,$proxyhost = false,$proxyport = false,$proxyusername = false, $proxypassword = false, $timeout = 0, $response_timeout = 30){ + parent::nusoap_base(); + $this->endpoint = $endpoint; + $this->proxyhost = $proxyhost; + $this->proxyport = $proxyport; + $this->proxyusername = $proxyusername; + $this->proxypassword = $proxypassword; + $this->timeout = $timeout; + $this->response_timeout = $response_timeout; + + // make values + if($wsdl){ + if (is_object($endpoint) && (get_class($endpoint) == 'wsdl')) { + $this->wsdl = $endpoint; + $this->endpoint = $this->wsdl->wsdl; + $this->wsdlFile = $this->endpoint; + $this->debug('existing wsdl instance created from ' . $this->endpoint); + } else { + $this->wsdlFile = $this->endpoint; + + // instantiate wsdl object and parse wsdl file + $this->debug('instantiating wsdl class with doc: '.$endpoint); + $this->wsdl = new wsdl($this->wsdlFile,$this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword,$this->timeout,$this->response_timeout); + } + $this->appendDebug($this->wsdl->getDebug()); + $this->wsdl->clearDebug(); + // catch errors + if($errstr = $this->wsdl->getError()){ + $this->debug('got wsdl error: '.$errstr); + $this->setError('wsdl error: '.$errstr); + } elseif($this->operations = $this->wsdl->getOperations()){ + $this->debug( 'got '.count($this->operations).' operations from wsdl '.$this->wsdlFile); + $this->endpointType = 'wsdl'; + } else { + $this->debug( 'getOperations returned false'); + $this->setError('no operations defined in the WSDL document!'); + } + } else { + $this->debug("instantiate SOAP with endpoint at $endpoint"); + $this->endpointType = 'soap'; + } + } + + /** + * calls method, returns PHP native type + * + * @param string $method SOAP server URL or path + * @param mixed $params An array, associative or simple, of the parameters + * for the method call, or a string that is the XML + * for the call. For rpc style, this call will + * wrap the XML in a tag named after the method, as + * well as the SOAP Envelope and Body. For document + * style, this will only wrap with the Envelope and Body. + * IMPORTANT: when using an array with document style, + * in which case there + * is really one parameter, the root of the fragment + * used in the call, which encloses what programmers + * normally think of parameters. A parameter array + * *must* include the wrapper. + * @param string $namespace optional method namespace (WSDL can override) + * @param string $soapAction optional SOAPAction value (WSDL can override) + * @param mixed $headers optional string of XML with SOAP header content, or array of soapval objects for SOAP headers + * @param boolean $rpcParams optional (no longer used) + * @param string $style optional (rpc|document) the style to use when serializing parameters (WSDL can override) + * @param string $use optional (encoded|literal) the use when serializing parameters (WSDL can override) + * @return mixed response from SOAP call + * @access public + */ + function call($operation,$params=array(),$namespace='http://tempuri.org',$soapAction='',$headers=false,$rpcParams=null,$style='rpc',$use='encoded'){ + $this->operation = $operation; + $this->fault = false; + $this->setError(''); + $this->request = ''; + $this->response = ''; + $this->responseData = ''; + $this->faultstring = ''; + $this->faultcode = ''; + $this->opData = array(); + + $this->debug("call: operation=$operation, namespace=$namespace, soapAction=$soapAction, rpcParams=$rpcParams, style=$style, use=$use, endpointType=$this->endpointType"); + $this->appendDebug('params=' . $this->varDump($params)); + $this->appendDebug('headers=' . $this->varDump($headers)); + if ($headers) { + $this->requestHeaders = $headers; + } + // serialize parameters + if($this->endpointType == 'wsdl' && $opData = $this->getOperationData($operation)){ + // use WSDL for operation + $this->opData = $opData; + $this->debug("found operation"); + $this->appendDebug('opData=' . $this->varDump($opData)); + if (isset($opData['soapAction'])) { + $soapAction = $opData['soapAction']; + } + if (! $this->forceEndpoint) { + $this->endpoint = $opData['endpoint']; + } else { + $this->endpoint = $this->forceEndpoint; + } + $namespace = isset($opData['input']['namespace']) ? $opData['input']['namespace'] : $namespace; + $style = $opData['style']; + $use = $opData['input']['use']; + // add ns to ns array + if($namespace != '' && !isset($this->wsdl->namespaces[$namespace])){ + $nsPrefix = 'ns' . rand(1000, 9999); + $this->wsdl->namespaces[$nsPrefix] = $namespace; + } + $nsPrefix = $this->wsdl->getPrefixFromNamespace($namespace); + // serialize payload + if (is_string($params)) { + $this->debug("serializing param string for WSDL operation $operation"); + $payload = $params; + } elseif (is_array($params)) { + $this->debug("serializing param array for WSDL operation $operation"); + $payload = $this->wsdl->serializeRPCParameters($operation,'input',$params); + } else { + $this->debug('params must be array or string'); + $this->setError('params must be array or string'); + return false; + } + $usedNamespaces = $this->wsdl->usedNamespaces; + if (isset($opData['input']['encodingStyle'])) { + $encodingStyle = $opData['input']['encodingStyle']; + } else { + $encodingStyle = ''; + } + $this->appendDebug($this->wsdl->getDebug()); + $this->wsdl->clearDebug(); + if ($errstr = $this->wsdl->getError()) { + $this->debug('got wsdl error: '.$errstr); + $this->setError('wsdl error: '.$errstr); + return false; + } + } elseif($this->endpointType == 'wsdl') { + // operation not in WSDL + $this->appendDebug($this->wsdl->getDebug()); + $this->wsdl->clearDebug(); + $this->setError( 'operation '.$operation.' not present.'); + $this->debug("operation '$operation' not present."); + return false; + } else { + // no WSDL + //$this->namespaces['ns1'] = $namespace; + $nsPrefix = 'ns' . rand(1000, 9999); + // serialize + $payload = ''; + if (is_string($params)) { + $this->debug("serializing param string for operation $operation"); + $payload = $params; + } elseif (is_array($params)) { + $this->debug("serializing param array for operation $operation"); + foreach($params as $k => $v){ + $payload .= $this->serialize_val($v,$k,false,false,false,false,$use); + } + } else { + $this->debug('params must be array or string'); + $this->setError('params must be array or string'); + return false; + } + $usedNamespaces = array(); + if ($use == 'encoded') { + $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/'; + } else { + $encodingStyle = ''; + } + } + // wrap RPC calls with method element + if ($style == 'rpc') { + if ($use == 'literal') { + $this->debug("wrapping RPC request with literal method element"); + if ($namespace) { + $payload = "<$operation xmlns=\"$namespace\">" . $payload . ""; + } else { + $payload = "<$operation>" . $payload . ""; + } + } else { + $this->debug("wrapping RPC request with encoded method element"); + if ($namespace) { + $payload = "<$nsPrefix:$operation xmlns:$nsPrefix=\"$namespace\">" . + $payload . + ""; + } else { + $payload = "<$operation>" . + $payload . + ""; + } + } + } + // serialize envelope + $soapmsg = $this->serializeEnvelope($payload,$this->requestHeaders,$usedNamespaces,$style,$use,$encodingStyle); + $this->debug("endpoint=$this->endpoint, soapAction=$soapAction, namespace=$namespace, style=$style, use=$use, encodingStyle=$encodingStyle"); + $this->debug('SOAP message length=' . strlen($soapmsg) . ' contents (max 1000 bytes)=' . substr($soapmsg, 0, 1000)); + // send + $return = $this->send($this->getHTTPBody($soapmsg),$soapAction,$this->timeout,$this->response_timeout); + if($errstr = $this->getError()){ + $this->debug('Error: '.$errstr); + return false; + } else { + $this->return = $return; + $this->debug('sent message successfully and got a(n) '.gettype($return)); + $this->appendDebug('return=' . $this->varDump($return)); + + // fault? + if(is_array($return) && isset($return['faultcode'])){ + $this->debug('got fault'); + $this->setError($return['faultcode'].': '.$return['faultstring']); + $this->fault = true; + foreach($return as $k => $v){ + $this->$k = $v; + $this->debug("$k = $v
"); + } + return $return; + } elseif ($style == 'document') { + // NOTE: if the response is defined to have multiple parts (i.e. unwrapped), + // we are only going to return the first part here...sorry about that + return $return; + } else { + // array of return values + if(is_array($return)){ + // multiple 'out' parameters, which we return wrapped up + // in the array + if(sizeof($return) > 1){ + return $return; + } + // single 'out' parameter (normally the return value) + $return = array_shift($return); + $this->debug('return shifted value: '); + $this->appendDebug($this->varDump($return)); + return $return; + // nothing returned (ie, echoVoid) + } else { + return ""; + } + } + } + } + + /** + * get available data pertaining to an operation + * + * @param string $operation operation name + * @return array array of data pertaining to the operation + * @access public + */ + function getOperationData($operation){ + if(isset($this->operations[$operation])){ + return $this->operations[$operation]; + } + $this->debug("No data for operation: $operation"); + } + + /** + * send the SOAP message + * + * Note: if the operation has multiple return values + * the return value of this method will be an array + * of those values. + * + * @param string $msg a SOAPx4 soapmsg object + * @param string $soapaction SOAPAction value + * @param integer $timeout set connection timeout in seconds + * @param integer $response_timeout set response timeout in seconds + * @return mixed native PHP types. + * @access private + */ + function send($msg, $soapaction = '', $timeout=0, $response_timeout=30) { + $this->checkCookies(); + // detect transport + switch(true){ + // http(s) + case preg_match('/^http/',$this->endpoint): + $this->debug('transporting via HTTP'); + if($this->persistentConnection == true && is_object($this->persistentConnection)){ + $http =& $this->persistentConnection; + } else { + $http = new soap_transport_http($this->endpoint); + if ($this->persistentConnection) { + $http->usePersistentConnection(); + } + } + $http->setContentType($this->getHTTPContentType(), $this->getHTTPContentTypeCharset()); + $http->setSOAPAction($soapaction); + if($this->proxyhost && $this->proxyport){ + $http->setProxy($this->proxyhost,$this->proxyport,$this->proxyusername,$this->proxypassword); + } + if($this->authtype != '') { + $http->setCredentials($this->username, $this->password, $this->authtype, array(), $this->certRequest); + } + if($this->http_encoding != ''){ + $http->setEncoding($this->http_encoding); + } + $this->debug('sending message, length='.strlen($msg)); + if(preg_match('/^http:/',$this->endpoint)){ + //if(strpos($this->endpoint,'http:')){ + $this->responseData = $http->send($msg,$timeout,$response_timeout,$this->cookies); + } elseif(preg_match('/^https/',$this->endpoint)){ + //} elseif(strpos($this->endpoint,'https:')){ + //if(phpversion() == '4.3.0-dev'){ + //$response = $http->send($msg,$timeout,$response_timeout); + //$this->request = $http->outgoing_payload; + //$this->response = $http->incoming_payload; + //} else + $this->responseData = $http->sendHTTPS($msg,$timeout,$response_timeout,$this->cookies); + } else { + $this->setError('no http/s in endpoint url'); + } + $this->request = $http->outgoing_payload; + $this->response = $http->incoming_payload; + $this->appendDebug($http->getDebug()); + $this->UpdateCookies($http->incoming_cookies); + + // save transport object if using persistent connections + if ($this->persistentConnection) { + $http->clearDebug(); + if (!is_object($this->persistentConnection)) { + $this->persistentConnection = $http; + } + } + + if($err = $http->getError()){ + $this->setError('HTTP Error: '.$err); + return false; + } elseif($this->getError()){ + return false; + } else { + $this->debug('got response, length='. strlen($this->responseData).' type='.$http->incoming_headers['content-type']); + return $this->parseResponse($http->incoming_headers, $this->responseData); + } + break; + default: + $this->setError('no transport found, or selected transport is not yet supported!'); + return false; + break; + } + } + + /** + * processes SOAP message returned from server + * + * @param array $headers The HTTP headers + * @param string $data unprocessed response data from server + * @return mixed value of the message, decoded into a PHP type + * @access private + */ + function parseResponse($headers, $data) { + $this->debug('Entering parseResponse() for data of length ' . strlen($data) . ' and type ' . $headers['content-type']); + if (!strstr($headers['content-type'], 'text/xml')) { + $this->setError('Response not of type text/xml'); + return false; + } + if (strpos($headers['content-type'], '=')) { + $enc = str_replace('"', '', substr(strstr($headers["content-type"], '='), 1)); + $this->debug('Got response encoding: ' . $enc); + if(preg_match('/^(ISO-8859-1|US-ASCII|UTF-8)$/i',$enc)){ + $this->xml_encoding = strtoupper($enc); + } else { + $this->xml_encoding = 'US-ASCII'; + } + } else { + // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1 + $this->xml_encoding = 'ISO-8859-1'; + } + $this->debug('Use encoding: ' . $this->xml_encoding . ' when creating soap_parser'); + $parser = new soap_parser($data,$this->xml_encoding,$this->operation,$this->decode_utf8); + // add parser debug data to our debug + $this->appendDebug($parser->getDebug()); + // if parse errors + if($errstr = $parser->getError()){ + $this->setError( $errstr); + // destroy the parser object + unset($parser); + return false; + } else { + // get SOAP headers + $this->responseHeaders = $parser->getHeaders(); + // get decoded message + $return = $parser->get_response(); + // add document for doclit support + $this->document = $parser->document; + // destroy the parser object + unset($parser); + // return decode message + return $return; + } + } + + /** + * sets the SOAP endpoint, which can override WSDL + * + * @param $endpoint string The endpoint URL to use, or empty string or false to prevent override + * @access public + */ + function setEndpoint($endpoint) { + $this->forceEndpoint = $endpoint; + } + + /** + * set the SOAP headers + * + * @param $headers mixed String of XML with SOAP header content, or array of soapval objects for SOAP headers + * @access public + */ + function setHeaders($headers){ + $this->requestHeaders = $headers; + } + + /** + * get the SOAP response headers (namespace resolution incomplete) + * + * @return string + * @access public + */ + function getHeaders(){ + return $this->responseHeaders; + } + + /** + * set proxy info here + * + * @param string $proxyhost + * @param string $proxyport + * @param string $proxyusername + * @param string $proxypassword + * @access public + */ + function setHTTPProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '') { + $this->proxyhost = $proxyhost; + $this->proxyport = $proxyport; + $this->proxyusername = $proxyusername; + $this->proxypassword = $proxypassword; + } + + /** + * if authenticating, set user credentials here + * + * @param string $username + * @param string $password + * @param string $authtype (basic|digest|certificate) + * @param array $certRequest (keys must be cainfofile (optional), sslcertfile, sslkeyfile, passphrase, verifypeer (optional), verifyhost (optional): see corresponding options in cURL docs) + * @access public + */ + function setCredentials($username, $password, $authtype = 'basic', $certRequest = array()) { + $this->username = $username; + $this->password = $password; + $this->authtype = $authtype; + $this->certRequest = $certRequest; + } + + /** + * use HTTP encoding + * + * @param string $enc + * @access public + */ + function setHTTPEncoding($enc='gzip, deflate'){ + $this->http_encoding = $enc; + } + + /** + * use HTTP persistent connections if possible + * + * @access public + */ + function useHTTPPersistentConnection(){ + $this->persistentConnection = true; + } + + /** + * gets the default RPC parameter setting. + * If true, default is that call params are like RPC even for document style. + * Each call() can override this value. + * + * This is no longer used. + * + * @return boolean + * @access public + * @deprecated since at least TYPO3 4.3, will be removed in TYPO3 4.5. + */ + function getDefaultRpcParams() { + t3lib_div::logDeprecatedFunction(); + + return $this->defaultRpcParams; + } + + /** + * sets the default RPC parameter setting. + * If true, default is that call params are like RPC even for document style + * Each call() can override this value. + * + * This is no longer used. + * + * @param boolean $rpcParams + * @access public + * @deprecated since at least TYPO3 4.3, will be removed in TYPO3 4.5. + */ + function setDefaultRpcParams($rpcParams) { + t3lib_div::logDeprecatedFunction(); + + $this->defaultRpcParams = $rpcParams; + } + + /** + * dynamically creates an instance of a proxy class, + * allowing user to directly call methods from wsdl + * + * @return object soap_proxy object + * @access public + */ + function getProxy(){ + $r = rand(); + $evalStr = $this->_getProxyClassCode($r); + //$this->debug("proxy class: $evalStr"; + // eval the class + eval($evalStr); + // instantiate proxy object + eval("\$proxy = new soap_proxy_$r('');"); + // transfer current wsdl data to the proxy thereby avoiding parsing the wsdl twice + $proxy->endpointType = 'wsdl'; + $proxy->wsdlFile = $this->wsdlFile; + $proxy->wsdl = $this->wsdl; + $proxy->operations = $this->operations; + $proxy->defaultRpcParams = $this->defaultRpcParams; + // transfer other state + $proxy->username = $this->username; + $proxy->password = $this->password; + $proxy->authtype = $this->authtype; + $proxy->proxyhost = $this->proxyhost; + $proxy->proxyport = $this->proxyport; + $proxy->proxyusername = $this->proxyusername; + $proxy->proxypassword = $this->proxypassword; + $proxy->timeout = $this->timeout; + $proxy->response_timeout = $this->response_timeout; + $proxy->http_encoding = $this->http_encoding; + $proxy->persistentConnection = $this->persistentConnection; + $proxy->requestHeaders = $this->requestHeaders; + $proxy->soap_defencoding = $this->soap_defencoding; + $proxy->endpoint = $this->endpoint; + $proxy->forceEndpoint = $this->forceEndpoint; + return $proxy; + } + + /** + * dynamically creates proxy class code + * + * @return string PHP/NuSOAP code for the proxy class + * @access private + */ + function _getProxyClassCode($r) { + if ($this->endpointType != 'wsdl') { + $evalStr = 'A proxy can only be created for a WSDL client'; + $this->setError($evalStr); + return $evalStr; + } + $evalStr = ''; + foreach ($this->operations as $operation => $opData) { + if ($operation != '') { + // create param string and param comment string + if (sizeof($opData['input']['parts']) > 0) { + $paramStr = ''; + $paramArrayStr = ''; + $paramCommentStr = ''; + foreach ($opData['input']['parts'] as $name => $type) { + $paramStr .= "\$$name, "; + $paramArrayStr .= "'$name' => \$$name, "; + $paramCommentStr .= "$type \$$name, "; + } + $paramStr = substr($paramStr, 0, strlen($paramStr)-2); + $paramArrayStr = substr($paramArrayStr, 0, strlen($paramArrayStr)-2); + $paramCommentStr = substr($paramCommentStr, 0, strlen($paramCommentStr)-2); + } else { + $paramStr = ''; + $paramCommentStr = 'void'; + } + $opData['namespace'] = !isset($opData['namespace']) ? 'http://testuri.com' : $opData['namespace']; + $evalStr .= "// $paramCommentStr + function " . str_replace('.', '__', $operation) . "($paramStr) { + \$params = array($paramArrayStr); + return \$this->call('$operation', \$params, '".$opData['namespace']."', '".(isset($opData['soapAction']) ? $opData['soapAction'] : '')."'); + } + "; + unset($paramStr); + unset($paramCommentStr); + } + } + $evalStr = 'class soap_proxy_'.$r.' extends soapclient { + '.$evalStr.' +}'; + return $evalStr; + } + + /** + * dynamically creates proxy class code + * + * @return string PHP/NuSOAP code for the proxy class + * @access public + */ + function getProxyClassCode() { + $r = rand(); + return $this->_getProxyClassCode($r); + } + + /** + * gets the HTTP body for the current request. + * + * @param string $soapmsg The SOAP payload + * @return string The HTTP body, which includes the SOAP payload + * @access private + */ + function getHTTPBody($soapmsg) { + return $soapmsg; + } + + /** + * gets the HTTP content type for the current request. + * + * Note: getHTTPBody must be called before this. + * + * @return string the HTTP content type for the current request. + * @access private + */ + function getHTTPContentType() { + return 'text/xml'; + } + + /** + * gets the HTTP content type charset for the current request. + * returns false for non-text content types. + * + * Note: getHTTPBody must be called before this. + * + * @return string the HTTP content type charset for the current request. + * @access private + */ + function getHTTPContentTypeCharset() { + return $this->soap_defencoding; + } + + /* + * whether or not parser should decode utf8 element content + * + * @return always returns true + * @access public + */ + function decodeUTF8($bool){ + $this->decode_utf8 = $bool; + return true; + } + + /** + * adds a new Cookie into $this->cookies array + * + * @param string $name Cookie Name + * @param string $value Cookie Value + * @return if cookie-set was successful returns true, else false + * @access public + */ + function setCookie($name, $value) { + if (strlen($name) == 0) { + return false; + } + $this->cookies[] = array('name' => $name, 'value' => $value); + return true; + } + + /** + * gets all Cookies + * + * @return array with all internal cookies + * @access public + */ + function getCookies() { + return $this->cookies; + } + + /** + * checks all Cookies and delete those which are expired + * + * @return always return true + * @access private + */ + function checkCookies() { + if (sizeof($this->cookies) == 0) { + return true; + } + $this->debug('checkCookie: check ' . sizeof($this->cookies) . ' cookies'); + $curr_cookies = $this->cookies; + $this->cookies = array(); + foreach ($curr_cookies as $cookie) { + if (! is_array($cookie)) { + $this->debug('Remove cookie that is not an array'); + continue; + } + if ((isset($cookie['expires'])) && (! empty($cookie['expires']))) { + if (strtotime($cookie['expires']) > time()) { + $this->cookies[] = $cookie; + } else { + $this->debug('Remove expired cookie ' . $cookie['name']); + } + } else { + $this->cookies[] = $cookie; + } + } + $this->debug('checkCookie: '.sizeof($this->cookies).' cookies left in array'); + return true; + } + + /** + * updates the current cookies with a new set + * + * @param array $cookies new cookies with which to update current ones + * @return always return true + * @access private + */ + function UpdateCookies($cookies) { + if (sizeof($this->cookies) == 0) { + // no existing cookies: take whatever is new + if (sizeof($cookies) > 0) { + $this->debug('Setting new cookie(s)'); + $this->cookies = $cookies; + } + return true; + } + if (sizeof($cookies) == 0) { + // no new cookies: keep what we've got + return true; + } + // merge + foreach ($cookies as $newCookie) { + if (!is_array($newCookie)) { + continue; + } + if ((!isset($newCookie['name'])) || (!isset($newCookie['value']))) { + continue; + } + $newName = $newCookie['name']; + + $found = false; + for ($i = 0; $i < count($this->cookies); $i++) { + $cookie = $this->cookies[$i]; + if (!is_array($cookie)) { + continue; + } + if (!isset($cookie['name'])) { + continue; + } + if ($newName != $cookie['name']) { + continue; + } + $newDomain = isset($newCookie['domain']) ? $newCookie['domain'] : 'NODOMAIN'; + $domain = isset($cookie['domain']) ? $cookie['domain'] : 'NODOMAIN'; + if ($newDomain != $domain) { + continue; + } + $newPath = isset($newCookie['path']) ? $newCookie['path'] : 'NOPATH'; + $path = isset($cookie['path']) ? $cookie['path'] : 'NOPATH'; + if ($newPath != $path) { + continue; + } + $this->cookies[$i] = $newCookie; + $found = true; + $this->debug('Update cookie ' . $newName . '=' . $newCookie['value']); + break; + } + if (! $found) { + $this->debug('Add cookie ' . $newName . '=' . $newCookie['value']); + $this->cookies[] = $newCookie; + } + } + return true; + } +} +?> \ No newline at end of file Index: typo3/sysext/em/mod1/clear.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: typo3\sysext\em\mod1\clear.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: typo3/sysext/em/mod1/conf.php =================================================================== --- typo3/sysext/em/mod1/conf.php (revision 0) +++ typo3/sysext/em/mod1/conf.php (revision 0) @@ -0,0 +1,12 @@ + \ No newline at end of file Index: typo3/sysext/em/mod1/download.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: typo3\sysext\em\mod1\download.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: typo3/sysext/em/mod1/em.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: typo3\sysext\em\mod1\em.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: typo3/sysext/em/mod1/index.php =================================================================== --- typo3/sysext/em/mod1/index.php (revision 0) +++ typo3/sysext/em/mod1/index.php (revision 0) @@ -0,0 +1,56 @@ + +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* A copy is found in the textfile GPL.txt and important notices to the license +* from the author is found in LICENSE.txt distributed with these scripts. +* +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ +/** + * Module: Extension manager + * + * $Id: index.php 7905 2010-06-13 14:42:33Z ohader $ + * + * @author Kasper Skaarhoj + * @author Karsten Dambekalns + */ + + +unset($MCONF); +require('conf.php'); +require($BACK_PATH.'init.php'); +require($BACK_PATH.'template.php'); +$BE_USER->modAccess($MCONF,1); + +require_once('class.em_index.php'); + +// Make instance: +$SOBE = t3lib_div::makeInstance('SC_mod_tools_em_index'); +$SOBE->init(); +foreach($SOBE->include_once as $INC_FILE) { + include_once($INC_FILE); +} +$SOBE->checkExtObj(); + +$SOBE->main(); +$SOBE->printContent(); +?> \ No newline at end of file Index: typo3/sysext/em/mod1/install.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: typo3\sysext\em\mod1\install.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: typo3/sysext/em/mod1/oodoc.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: typo3\sysext\em\mod1\oodoc.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Index: typo3/sysext/em/mod1/uninstall.gif =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: typo3\sysext\em\mod1\uninstall.gif ___________________________________________________________________ Added: svn:mime-type + application/octet-stream