<?php
/**
* Reads a plugin on the filesystem and constructs descriptor objects
* * @package stickleback
*
* Copyright (c) 2007 Yahoo! Inc. All rights reserved.
* The copyrights embodied in the content of this file are licensed under the BSD
* open source license
*
* @subpackage plugin
* @author Matt Zandstra <hide@address.com>
* @version CVS: $Id: PluginResolver.php,v 1.13 2008-10-24 19:27:02 zandstra Exp $
*/
/**
* requires
*/
require_once("stickleback/PluginAutoload.php");
require_once("stickleback/Exception.php");
require_once("stickleback/Logger.php");
require_once("stickleback/PluginSet.php");
require_once("stickleback/FSPluginObserver.php");
require_once("stickleback/PluginXml.php");
/**
* Reads a plugin on the filesystem and constructs descriptor objects
*
* @package stickleback
* @subpackage plugin
* @author Matt Zandstra <hide@address.com>
* @version CVS: $Id: PluginResolver.php,v 1.13 2008-10-24 19:27:02 zandstra Exp $
*/
class stickleback_PluginResolver {
private $pluginDirs = array();
private $xml_name;
private $observers = array();
/**
* Constructor
* @param array Directories to search for plugins
*/
function __construct( array $pluginDirs, $xml_name='sbplugin.xml' ) {
$this->pluginDirs = $pluginDirs;
$this->xml_name = $xml_name;
}
/**
* accepts a {@link stickleback_FSPluginObserver} object for later notification
*
* @param array Directories to search for plugins
*/
function accept( stickleback_FSPluginObserver $observer ) {
$this->observers[] = $observer;
}
/**
* work through the directories in the plugin directories looking for valid plugins
*
* Observers are notified for each pluginset:
* {@link stickleback_FSPluginObserver::notifyFoundPlugin()} will be called for
* every found plugin.
* {@link stickleback_FSPluginObserver::notifyNonPlugin()} will be called for
* every found pluginset that turns out not to be a plugin
*
* The criteria for recognising a plugin is very basic. Just a folder with a file
* of the same name (followed by .php). Proper parsing then takes place elsewhere
* @param array Directories to search for plugins
* @todo support plugin.xml format
*/
function findPlugins() {
$xml_handler = new stickleback_PluginXml();
$visited = array();
foreach ( $this->pluginDirs as $dir ) {
$dir = self::resolvePath( $dir );
if ( isset( $visited[$dir] ) ) {
L::debug("already seen '$dir'");
continue;
}
$visited[$dir]=1;
$dirit = new DirectoryIterator($dir);
foreach ($dirit as $pluginset) {
if ( $pluginset->isDot() || !$pluginset->isDir() ) {
continue;
}
$dirpath = $dir.DIRECTORY_SEPARATOR.$pluginset;
$sbpluginxml_path = $dirpath.DIRECTORY_SEPARATOR.$this->xml_name;
$pluginset_obj = new stickleback_PluginSet( $pluginset->getFilename(), $dirpath );
$descriptors = $xml_handler->readFile( $sbpluginxml_path );
foreach ( $descriptors as $desc ) {
$this->checkDescriptorAgainstFileSystem( $dirpath, $desc );
// REFACTOR: let the registry handle this
$desc->setPluginSet( $pluginset_obj );
foreach ( $this->observers as $observer ) {
$observer->notifyFoundPlugin( $desc );
}
}
}
}
}
private function checkDescriptorAgainstFileSystem( $path, stickleback_PluginDescriptor $desc ) {
// $path is guaranteed absolute
$plugin_name = $desc->getId();
$class_name = $desc->getType();
// check that the plug-in exists on the filesystem and the class exists after inclusion
// $classfilepath = $path.DIRECTORY_SEPARATOR."{$plugin_name}.php";
// $this->ensure( file_exists( $classfilepath ), "no such class file: '$classfilepath'" );
// require_once( $classfilepath );
$rclass = new ReflectionClass( $class_name );
$this->ensure( class_exists( $class_name ), "could not find class '$class_name' for plugin '$plugin_name'" );
/*
// make sure the class in question _is_ a stickleback_Plugin
if ( ! $rclass->implementsInterface( 'stickleback_Plugin' ) ) {
throw new stickleback_Exception("'$class_name' does not implement 'stickleback_Plugin'");
}
*/
// check that the plug-in implements the interfaces it promises to
$honors = $desc->mustImplement();
foreach ( $honors as $honor ) {
list( $plugin, $interface ) = explode( ".", $honor );
//if ( ! $rclass->implementsInterface( $interface ) ) {
if ( ! interface_exists( $interface ) ) {
throw new stickleback_Exception("extension point interface '$interface' must exist" );
}
if ( ! $rclass->implementsInterface( $interface ) ) {
throw new stickleback_Exception("plug-in '$class_name' must implement interface: '$interface'" );
}
}
$offers = $desc->getExtensionPointDescriptors();
foreach ( $offers as $epoint ) {
$interface_name = $epoint->getType();
if ( ! interface_exists( $interface_name ) ) {
throw new stickleback_Exception("plug-in '$class_name' offers extension point interface '$interface_name' which does not exist" );
}
}
}
private function ensure( $bool, $msg ) {
if ( ! $bool ) { throw new stickleback_Exception( $msg ); }
}
/**
* static method that uses the include path to search for directories/files on the filesystem
*
* If the given entity exists, or if it cannot be found, the original string is returned.
* Otherwise the matching path is returned
*
* @param string the file/directory to test for
* @return string the resolved file/directory (or original string)
*/
static function resolvePath( $dir ) {
if ( @file_exists( $dir ) ) {
return $dir;
}
$sep = DIRECTORY_SEPARATOR;
if ( strpos( $dir, DIRECTORY_SEPARATOR ) === 0 ) {
return $dir;
}
$pathArray = explode( PATH_SEPARATOR, get_include_path() );
foreach ( $pathArray as $path ) {
$fullpath = $path.$sep.$dir;
if ( @file_exists( $fullpath ) ) {
$fullpath = preg_replace( '/\\'.$sep.'+/', $sep, $fullpath );
return $fullpath;
}
}
return $dir;
}
}
?>