#!/bin/env php PHK Manager - File: /src/PHK/Backend.php
Package Home

PHK Manager

PHK Home

File: /src/PHK/Backend.php

Size:20154
Storage flags:

<?php
//=============================================================================
//
// Copyright Francois Laupretre <phk@tekwire.net>
//
//   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 <phk@tekwire.net>
* @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).
*
* <Public API>
*/

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=='' '<none>' $string);
self::endInfoTable($html);
}

//---------------------------------
// Display the file tree

public function showfiles()
{
$this->proxy()->showfiles();
}

//---------------------------------
// Display map content or <empty> 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";
}

//-----
/**
* <Info> 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 '<p>' '')."Not defined\n";
    return;
    }

if (
$this->isCallablePluginMethod('_webinfo'))
    {
    
$this->callPluginMethod('_webinfo',$html);
    echo 
$html '<p>' "\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);
}

//-----
/**
* <Info> Displays an option and its value
*
* An URL can be set in an option. Syntax: 'text to display <url>'
* 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);
}

//-----
/**
* <Info> 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 '<h2>'.htmlspecialchars($title).'</h2>'
    
"\n==== ".str_pad($title.' ',70,'='). "\n\n";
}

//-----
/**
* <Info> 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 
'<tr><td>'.htmlspecialchars($string).':&nbsp;</td><td>';
    if (
$url)
        {
        echo 
'<a href="'.$url.'"';
        if (
$newwin) echo ' target="_blank"';
        echo 
'>';
        }
    echo 
htmlspecialchars($value);
    if (
$url) echo '</a>';
    echo 
'</td></tr>';
    }
else
    {
    echo 
"$string$value";
    if ((!
is_null($url)) && ($url{0}!='/')) echo " <$url>";
    echo 
"\n";
    }
}

//-----
/**
* <Info> 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 '<table border=0>' '';
}

//-----

public static function endInfoTable($html)
{
echo 
$html '</table>' '';
}

//-----
// 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='<none>';
    
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='<none>';
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 '<p>' '')."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 $pathself::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 ? ('<pre>'.htmlspecialchars($data).'</pre>') : $data);

//-- Sections

self::infoSection($html,'Sections');
$this->proxy()->stree()->display(false);
}

//-----
/**
* Returns a subfile content for a multi-type metafile
*
* File name : <prefix>.<type> - 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 
'<pre>'.htmlspecialchars(\PHK\Tools\Util::readFile($base_path.$suffix))
                .
'</pre>';
    }

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='<No '.$name.' file>'."\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 <path>       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 <name>    Display a package option\n";
echo 
"    - @set_interp <string>  Set the first line of the PHK to '#!<string>'\n";
echo 
"    - @info             Display information about the PHK file\n";
echo 
"    - @techinfo         Display technical information\n";
echo 
"    - @dump <directory> 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
//===========================================================================
?>

For more information about the PHK package format: http://phk.tekwire.net