// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //============================================================================= /** * @copyright Francois Laupretre * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, V 2.0 * @category PHK * @package PHK *///========================================================================== namespace PHK { if (!class_exists('PHK\Backend',false)) { //============================================================================= /** * This class contains the non-accelerated runtime code. This code must * never be accessed during 'fast path' scenarios. * * Each \PHK\Backend instance is associated with a 'front-end' PHK instance * (accelerated or not). * * */ class Backend { private $front; // PHK front-end //-------------- public function __construct($front) { $this->front=$front; } //--------------------------------- public function __get($name) { return $this->front->$name(); } //--------------------------------- public function __call($method,$args) { return \PHK\Tools\Util::callMethod($this->front,$method,$args); } //--------------------------------- public function test() { // Remove E_NOTICE messages if the test script is a package - workaround // to PHP bug #39903 ('__COMPILER_HALT_OFFSET__ already defined') error_reporting(($errlevel=error_reporting()) & ~E_NOTICE); if (!is_null($test_script=$this->option('test_script'))) { $test_uri=$this->uri($test_script); require($test_uri); } elseif (!is_null($phpunit_test_package=$this->option('phpunit_test_package'))) { if (!is_null($phpunit_package=$this->option('phpunit_package'))) { $phpunit_package_mnt=require $this->uri($phpunit_package); } else $phpunit_package_mnt=null; $phpunit_test_package_mnt=require $this->uri($phpunit_test_package); \PHK\UnitTest\_phk_load_phpunit_interface(); define('PHPUnit_MAIN_METHOD', 'PHPUnit_TextUI_\PHK::main'); PHPUnit_TextUI_\PHK::main(); if (!is_null($phpunit_package_mnt)) \PHK\Mgr::umount($phpunit_package_mnt); if (!is_null($phpunit_test_package_mnt)) \PHK\Mgr::umount($phpunit_test_package_mnt); } else echo "No unit tests\n"; error_reporting($errlevel); } //--------------------------------- // Display the environment // This function cannot be cached public function envinfo() { $html=\PHK\Tools\Util::envIsWeb(); //-- Accelerator self::infoSection($html,'PHK Accelerator'); self::startInfoTable($html); if (\PHK::acceleratorIsPresent()) \PHK::accelTechInfo(); else self::showInfoLine($html,'PHK Accelerator','No'); self::infoSection($html,'Cache'); self::showInfoLine($html,'Cache system used',\PHK\Cache::cacheName()); self::endInfoTable($html); //-- Environment self::infoSection($html,'Environment'); self::startInfoTable($html); self::showInfoLine($html,'PHP SAPI',php_sapi_name()); self::showInfoLine($html,'Mount point',$this->mnt); //-- Mount options $string=''; $class=new ReflectionClass('\PHK'); foreach($class->getConstants() as $name => $value) { if ((strlen($name)>1) && (substr($name,0,2)=='F_') && ($this->flags & $value)) $string .= ','.strtolower(substr($name,2)); } unset($class); $string=trim($string,','); self::showInfoLine($html,'Current mount options' ,$string=='' ? '' : $string); self::endInfoTable($html); } //--------------------------------- // Display the file tree public function showfiles() { $this->proxy()->showfiles(); } //--------------------------------- // Display map content or message public function showmap($subfile_to_url_function=null) { if ($this->mapDefined()) \Automap\Mgr::map($this->automapID)->show(null,$subfile_to_url_function); else echo "This package does not contain a map\n"; } //----- /** * Displays information about the plugin * * If the plugin object defines a method name '_webinfo', this method is called * with the $html parameter. * * If the plugin is not defined, just displays a small informative message * * @param boolean $html Whether to display in html or raw text * @return void */ private function pluginInfo($html) { self::infoSection($html,'Plugin'); if (is_null($class=$this->option('plugin_class'))) { echo ($html ? '

' : '')."Not defined\n"; return; } if ($this->isCallablePluginMethod('_webinfo')) { $this->callPluginMethod('_webinfo',$html); echo $html ? '

' : "\n"; } self::startInfoTable($html); self::showInfoLine($html,'Class',$class); $rc=new ReflectionClass($class); foreach ($rc->getMethods() as $method) { if ((!$method->isPublic())||($method->isStatic()) ||($method->isConstructor())||($method->isDestructor()) ||($method->getName()==='_webinfo')) continue; $name=$method->getName(); $a=array(); foreach($method->getParameters() as $param) { $s='$'.$param->getName(); if ($param->isPassedByReference()) $s='&'.$s; if ($param->isArray()) $s = 'Array '.$s; if ($param->isOptional()) { if ($param->isDefaultValueAvailable()) $s .= ' = '.var_export($param->getDefaultValue(),true); $s = '['.$s.']'; } $a[]=$s; } self::showInfoLine($html,'Method',$name.' ( '.implode(', ',$a).' )'); } self::endInfoTable($html); } //----- /** * Displays an option and its value * * An URL can be set in an option. Syntax: 'text to display ' * In HTML mode, only the text is displayed and an hyperlink is generated * In text mode, it is displayed as-is. * URLs starting with 'http://' are automatically recognized * @param boolean $html Whether to display in html or raw text * @param string $opt Option name * @return void */ private function showOption($html,$opt,$default=null) { $str1=ucfirst(str_replace('_',' ',$opt)); $url=null; $newwin=true; if (is_null($val=$this->option($opt))) $val=$default; if ($html && preg_match('/^(.*)\s<(\S+)>.*$/',$val,$regs)) { // If the value contains an URL $str2=trim($regs[1]); $url=$regs[2]; if ($str2=='') $str2=$url; } else { $str2=$val; $vlen=strlen($val); if (($vlen>=7)&&(substr($val,0,7)=='http://')) $url=$val; elseif (($vlen>=1) && ($val{0}=='/') && file_exists($this->uri($val))) { // Warning: We build an HTTP URL going to \PHK\Web\Info, not a stream wrapper URI. $url=\PHK::subpathURL('/view/'.trim($val,'/')); $newwin=false; } } self::showInfoLine($html,$str1,$str2,$url,$newwin); } //----- /** * Start a new information section * * @param boolean $html Whether to display in html or raw text * @param string $title The title to display * @return void */ public static function infoSection($html,$title) { echo $html ? '

'.htmlspecialchars($title).'

' : "\n==== ".str_pad($title.' ',70,'='). "\n\n"; } //----- /** * Displays an information line * * In html mode, the information is displayed in a table. This table must * have been opened by a previous call to \PHK::startInfoTable(). * * Note: The URLs starting with a '/' char are internal (generated by \PHK\Web\Info * ) and, so, are displayed in html mode only. * * @param boolean $html Whether to display in html or raw text * @param string $string The left side (without ':') * @param string|boolean $value The value to display. If boolean, displays 'Yes' * or 'No'. * @param string|null $url An URL to associate with this value. Null if no URL. * @param boolean $newwin Used in html mode only. Whether the URL link opens * a new window when clicked. * @return void * * @see startInfoTable() * @see endInfoTable() */ public static function showInfoLine($html,$string,$value,$url=null ,$newwin=true) { if (is_null($value)) $value='<>'; if (is_bool($value)) $value=\PHK\Tools\Util::bool2str($value); if ($html) { echo ''.htmlspecialchars($string).': '; if ($url) { echo ''; } echo htmlspecialchars($value); if ($url) echo ''; echo ''; } else { echo "$string: $value"; if ((!is_null($url)) && ($url{0}!='/')) echo " <$url>"; echo "\n"; } } //----- /** * Starts an HTML table * * In text mode, does nothing. * * This function is public because it can be called from the plugin's _webinfo * method. * * @param boolean $html Whether to display in html or raw text * @return void */ public static function startInfoTable($html) { echo $html ? '' : ''; } //----- public static function endInfoTable($html) { echo $html ? '
' : ''; } //----- // Display non technical information // Webinfo default welcome page public function info() { $html=\PHK\Tools\Util::envIsWeb(); if ($html && (!is_null($info_script=$this->option('info_script')))) { require($this->uri($info_script)); } else { self::startInfoTable($html); $this->showOption($html,'name'); $this->showOption($html,'summary'); $this->showOption($html,'version'); $this->showOption($html,'release'); $this->showOption($html,'distribution'); $this->showOption($html,'license'); $this->showOption($html,'copyright'); $this->showOption($html,'url'); $this->showOption($html,'author'); $this->showOption($html,'packager'); $this->showOption($html,'requires'); $req=implode(' ',\PHK\Tools\Util::mkArray($this->option('required_extensions'))); if ($req=='') $req=''; self::showInfoLine($html,'Required extensions',$req); self::endInfoTable($html); } } //----- public function techinfo() { $html=\PHK\Tools\Util::envIsWeb(); self::infoSection($html,'Package'); self::startInfoTable($html); $this->showOption($html,'name'); $this->showOption($html,'summary'); $this->showOption($html,'version'); $this->showOption($html,'release'); $this->showOption($html,'distribution'); $this->showOption($html,'license'); $this->showOption($html,'copyright'); $this->showOption($html,'url'); $this->showOption($html,'author'); $this->showOption($html,'packager'); $this->showOption($html,'requires'); self::showInfoLine($html,'Signed',$this->proxy()->signed()); self::showInfoLine($html,'Automap defined',$this->mapDefined()); self::showInfoLine($html,'File path',$this->path); self::showInfoLine($html,'File size',filesize($this->path)); $req=implode(', ',\PHK\Tools\Util::mkArray($this->option('required_extensions'))); if ($req=='') $req=''; self::showInfoLine($html,'Required extensions',$req); self::showInfoLine($html,'Build date' ,\PHK\Tools\Util::timeString($this->buildInfo('build_timestamp'))); $this->showOption($html,'icon'); $this->showOption($html,'crc_check',false); $this->showOption($html,'help_prefix'); $this->showOption($html,'license_prefix'); $this->showOption($html,'auto_umount',false); $this->showOption($html,'no_cache',false); $this->showOption($html,'no_opcode_cache',false); $this->showOption($html,'prolog_code_creator',false); $this->showOption($html,'plain_prolog',false); self::showInfoLine($html,'File count',count($this->pathList())); self::endInfoTable($html); $this->pluginInfo($html); self::infoSection($html,'Package scripts'); self::startInfoTable($html); $this->showOption($html,'cli_run_script'); $this->showOption($html,'web_run_script'); $this->showOption($html,'lib_run_script'); $this->showOption($html,'info_script'); $this->showOption($html,'mount_script'); $this->showOption($html,'umount_script'); $this->showOption($html,'test_script'); $this->showOption($html,'phpunit_package'); $this->showOption($html,'phpunit_test_package'); self::endInfoTable($html); self::infoSection($html,'Module versions'); self::startInfoTable($html); self::showInfoLine($html,'PHK Creator',$this->buildInfo('phk_creator_version')); self::showInfoLine($html,'Automap Creator',$this->buildInfo('automap_creator_version')); self::showInfoLine($html,'Automap min version',$this->buildInfo('automap_minVersion')); self::endInfoTable($html); self::infoSection($html,'Sub-packages'); ob_start(); $this->proxy()->displayPackages(); $data=ob_get_clean(); if ($data==='') echo ($html ? '

' : '')."None\n"; else echo $data; self::infoSection($html,'Web direct access'); self::startInfoTable($html); $list=\PHK\Tools\Util::mkArray($this->option('web_access')); self::showInfoLine($html,'State',count($list) ? 'Enabled' : 'Disabled'); $this->showOption($html,'web_main_redirect',false); foreach($list as $path) self::showInfoLine($html,'Path',$path); self::endInfoTable($html); //-- Options self::infoSection($html,'Package options'); $a=$this->options(); $data=(is_null($a) ? '<>' : print_r($a,true)); echo ($html ? ('

'.htmlspecialchars($data).'
') : $data); //-- Sections self::infoSection($html,'Sections'); $this->proxy()->stree()->display(false); } //----- /** * Returns a subfile content for a multi-type metafile * * File name : . - Type is 'txt' or 'htm'. * * A text file can be transformed to html, but the opposite is not possible. * * Type is determined by the SAPI type (CLI => txt, else => htm). * * @param string $prefix Prefix to search for * @return string|null The requested content or null if not found */ public function autoFile($prefix) { $html=\PHK\Tools\Util::envIsWeb(); $txt_suffixes=array('.txt',''); $suffixes=($html ? array('.htm','.html') : $txt_suffixes); $base_path=$this->uri($prefix); foreach($suffixes as $suffix) { if (is_readable($base_path.$suffix)) { return \PHK\Tools\Util::readFile($base_path.$suffix); break; } } // If html requested and we only have a txt file, tranform it to html if ($html) { foreach ($txt_suffixes as $suffix) if (is_readable($base_path.$suffix)) return '
'.htmlspecialchars(\PHK\Tools\Util::readFile($base_path.$suffix))
				.'
'; } return null; } //----- /** * Returns a multi-type content from an option name * * Option ($name.'_prefix') gives the prefix to send to autoFile() * * @param string $name Option prefix * @return string Requested content or an informative error string. */ public function autoOption($name) { $data=null; $prefix=$this->option($name.'_prefix'); if (!is_null($prefix)) $data=$this->autoFile($prefix); if (is_null($data)) { $data=''."\n"; if (\PHK\Tools\Util::envIsWeb()) $data=htmlspecialchars($data); } return $data; } //----- /** * Checks if the plugin class is defined and contains a given method * * @param string $method * @return boolean */ public function isCallablePluginMethod($method) { return (is_null($this->plugin)) ? false : is_callable(array($this->plugin,$method)); } //----- /** * Calls a given method in the plugin object * * @param string method * @return * the method's return value * @throws \Exception if the plugin or the method does not exist */ public function callPluginMethod($method) { if (!$this->isCallablePluginMethod($method)) throw new \Exception($method.': Undefined plugin method'); $args=func_get_args(); array_shift($args); return call_user_func_array(array($this->plugin,$method),$args); } //----- public function pathList() { return unserialize(file_get_contents($this->commandURI(__FUNCTION__))); } //----- public function sectionList() { return unserialize(file_get_contents($this->commandURI(__FUNCTION))); } //----- /** * Check a package * * TODO: There's a lot more to check... * * @return array of error messages */ public function check() { $errors=array(); // Check package CRC try { $this->proxy()->crcCheck(); } catch (\Exception $e) { $errors[]=$e->getMessage(); } // Check symbol map $id=$this->automapID(); if ($id) { $map=\Automap\Mgr::map($id); $errors=array_merge($errors,$map->check()); } return $errors; } //--------------------------------- // Workaround for PHP bug/issue when trying to use PATH_INFO when PHP is // run as an Apache CGI executable. In this mode, an url in the form of // 'http://.../.../file.php/args' does not go to file.php but returns // 'No input file specified'. There, we have to pass args the 'usual' // way (via $_REQUEST). // Drawback: as the URL now contains a '?' char, most browsers refuse to cache // it, even with the appropriate header fields, causing some useless traffic // when navigating in the tabs and flicking on the screen. So, the preferred // method is via PATH_INFO. // Allows a PHK package to become fully compatible with CGI mode by computing // every relative URLs through this method. public static function subpathURL($path) { if ($path{0}!='/') $path=\PHK::setSubpath().'/'.$path; //-- Make path absolute $path=preg_replace(',//+,','/',$path); return \PHK\Tools\Util::httpBaseURL().((php_sapi_name()=='cgi') ? ('?_phk_path='.urlencode($path)) : $path); } //----- private static function cmdUsage($msg=null) { if (!is_null($msg)) echo "** ERROR: $msg\n"; echo "\nAvailable commands:\n\n"; echo " - @help Display package help\n"; echo " - @license Display license\n"; echo " - @get Display a subfile content\n"; echo " - @showmap Display symbol map, if present\n"; echo " - @showfiles List subfiles\n"; echo " - @check Check package\n"; echo " - @option Display a package option\n"; echo " - @set_interp Set the first line of the PHK to '#!'\n"; echo " - @info Display information about the PHK file\n"; echo " - @techinfo Display technical information\n"; echo " - @dump Extracts the files\n"; echo " - @test [switches] [UnitTest] Run the package's unit tests\n"; if (!is_null($msg)) exit(1); } //----- public function builtinProlog($file) { $retcode=0; $args=$_SERVER['argv']; try { $this->proxy()->crcCheck(); $command=\PHK\Tools\Util::substr($args[1],1); array_shift($args); $param=isset($args[1]) ? $args[1] : null; switch($command) { case 'get': if (is_null($param)) self::cmdUsage($command.": needs argument"); $uri=$this->uri($param); if (is_file($uri)) readfile($uri); else throw new \Exception("$param: file not found"); break; case 'test': case 'showmap': case 'info': case 'techinfo': case 'showfiles': $this->$command(); break; case 'check': $errs=$this->check(); if (count($errs)) { foreach($errs as $err) echo "$err\n"; throw new \Exception("*** The check procedure found errors in $phk_path"); } echo "Check OK\n"; break; case 'option': $res=$this->$command($param); if (is_null($res)) throw new \Exception('Option not set'); echo "$res\n"; break; case 'set_interp': if (is_null($param)) self::cmdUsage($command.": needs argument"); //-- This is the only place in the runtime code where we write something //-- into an existing PHK archive. if (file_put_contents($file ,\PHK\Proxy::setBufferInterp($file,$param))===false) throw new \Exception('Cannot write file'); break; case 'license': case 'licence': echo $this->autoOption('license'); break; case 'help': echo $this->autoOption($command); break; case 'dump': if (is_null($param)) self::cmdUsage($command.": needs argument"); $this->proxy()->ftree()->dump($param); break; case '': self::cmdUsage(); break; default: self::cmdUsage($command.': Unknown command'); } \PHK\Tools\Util::displaySlowPath(); } catch (\Exception $e) { if (getenv('SHOW_EXCEPTION')!==false) throw $e; echo "** ERROR: Command failed ($command) - ".$e->getMessage()."\n"; $retcode=1; } return $retcode; } //--- } // End of class //=========================================================================== } // End of class_exists //=========================================================================== } // End of namespace //=========================================================================== ?>