mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge in Andrew Shorts ManifestBuilder rewrite
This commit is contained in:
commit
ce5648a700
@ -121,68 +121,9 @@ class RestfulService extends ViewableData {
|
||||
$response = unserialize($store);
|
||||
|
||||
} else {
|
||||
$ch = curl_init();
|
||||
$timeout = 5;
|
||||
$useragent = "SilverStripe/" . SapphireInfo::Version();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
|
||||
if(!ini_get('open_basedir')) curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||
|
||||
// Add headers
|
||||
if($this->customHeaders) {
|
||||
$headers = array_merge((array)$this->customHeaders, (array)$headers);
|
||||
}
|
||||
|
||||
if($headers) curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
|
||||
// Add authentication
|
||||
if($this->authUsername) curl_setopt($ch, CURLOPT_USERPWD, "$this->authUsername:$this->authPassword");
|
||||
|
||||
// Add fields to POST and PUT requests
|
||||
if($method == 'POST') {
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||
}
|
||||
else if($method == 'PUT') {
|
||||
$put = fopen("php://temp", 'r+');
|
||||
fwrite($put, $data);
|
||||
fseek($put, 0);
|
||||
|
||||
curl_setopt($ch, CURLOPT_PUT, 1);
|
||||
curl_setopt($ch, CURLOPT_INFILE, $put);
|
||||
curl_setopt($ch, CURLOPT_INFILESIZE, strlen($data));
|
||||
}
|
||||
$response = $this->curlRequest($url, $method, $data, $headers, $curlOptions);
|
||||
|
||||
// Apply proxy settings
|
||||
if(is_array($this->proxy)) {
|
||||
curl_setopt_array($ch, $this->proxy);
|
||||
}
|
||||
|
||||
// Set any custom options passed to the request() function
|
||||
curl_setopt_array($ch, $curlOptions);
|
||||
|
||||
// Run request
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
$responseBody = curl_exec($ch);
|
||||
$curlError = curl_error($ch);
|
||||
|
||||
// Problem verifying the server SSL certificate; just ignore it as it's not mandatory
|
||||
if(strpos($curlError,'14090086') !== false) {
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
$responseBody = curl_exec($ch);
|
||||
$curlError = curl_error($ch);
|
||||
}
|
||||
|
||||
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
if($curlError !== '' || $statusCode == 0) $statusCode = 500;
|
||||
|
||||
$response = new RestfulService_Response($responseBody, $statusCode);
|
||||
curl_close($ch);
|
||||
|
||||
if($curlError === '' && !$response->isError()) {
|
||||
if(!$response->isError()) {
|
||||
// Serialise response object and write to cache
|
||||
$store = serialize($response);
|
||||
file_put_contents($cache_path, $store);
|
||||
@ -204,7 +145,83 @@ class RestfulService extends ViewableData {
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Actually performs a remote service request using curl. This is used by
|
||||
* {@link RestfulService::request()}.
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
* @param array $data
|
||||
* @param array $headers
|
||||
* @param array $curlOptions
|
||||
* @return RestfulService_Response
|
||||
*/
|
||||
public function curlRequest($url, $method, $data = null, $headers = null, $curlOptions = array()) {
|
||||
$ch = curl_init();
|
||||
$timeout = 5;
|
||||
$useragent = 'SilverStripe/' . SapphireInfo::Version();
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
|
||||
if(!ini_get('open_basedir')) curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||
|
||||
// Add headers
|
||||
if($this->customHeaders) {
|
||||
$headers = array_merge((array)$this->customHeaders, (array)$headers);
|
||||
}
|
||||
|
||||
if($headers) curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
|
||||
// Add authentication
|
||||
if($this->authUsername) curl_setopt($ch, CURLOPT_USERPWD, "$this->authUsername:$this->authPassword");
|
||||
|
||||
// Add fields to POST and PUT requests
|
||||
if($method == 'POST') {
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||
} elseif($method == 'PUT') {
|
||||
$put = fopen("php://temp", 'r+');
|
||||
fwrite($put, $data);
|
||||
fseek($put, 0);
|
||||
|
||||
curl_setopt($ch, CURLOPT_PUT, 1);
|
||||
curl_setopt($ch, CURLOPT_INFILE, $put);
|
||||
curl_setopt($ch, CURLOPT_INFILESIZE, strlen($data));
|
||||
}
|
||||
|
||||
// Apply proxy settings
|
||||
if(is_array($this->proxy)) {
|
||||
curl_setopt_array($ch, $this->proxy);
|
||||
}
|
||||
|
||||
// Set any custom options passed to the request() function
|
||||
curl_setopt_array($ch, $curlOptions);
|
||||
|
||||
// Run request
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
$responseBody = curl_exec($ch);
|
||||
$curlError = curl_error($ch);
|
||||
|
||||
// Problem verifying the server SSL certificate; just ignore it as it's not mandatory
|
||||
if(strpos($curlError,'14090086') !== false) {
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
$responseBody = curl_exec($ch);
|
||||
$curlError = curl_error($ch);
|
||||
}
|
||||
|
||||
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
if($curlError !== '' || $statusCode == 0) $statusCode = 500;
|
||||
|
||||
$response = new RestfulService_Response($responseBody, $statusCode);
|
||||
curl_close($ch);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a full request url
|
||||
* @param string
|
||||
|
163
core/ClassInfo.php
Executable file → Normal file
163
core/ClassInfo.php
Executable file → Normal file
@ -12,16 +12,14 @@ class ClassInfo {
|
||||
* @todo Improve documentation
|
||||
*/
|
||||
static function allClasses() {
|
||||
global $_ALL_CLASSES;
|
||||
return $_ALL_CLASSES['exists'];
|
||||
return SS_ClassLoader::instance()->allClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Improve documentation
|
||||
*/
|
||||
static function exists($class) {
|
||||
global $_ALL_CLASSES;
|
||||
return isset($_ALL_CLASSES['exists'][$class]) ? $_ALL_CLASSES['exists'][$class] : null;
|
||||
return SS_ClassLoader::instance()->classExists($class);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,54 +57,54 @@ class ClassInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the database tables linked to this class.
|
||||
* Gets an array of the current class, it subclasses and its ancestors. It then filters that list
|
||||
* to those with DB tables
|
||||
* Returns an array of the current class and all its ancestors and children
|
||||
* which have a DB table.
|
||||
*
|
||||
* @param mixed $class string of the classname or instance of the class
|
||||
* @param string|object $class
|
||||
* @todo Move this into data object
|
||||
* @return array
|
||||
*/
|
||||
static function dataClassesFor($class) {
|
||||
global $_ALL_CLASSES;
|
||||
if (is_object($class)) $class = get_class($class);
|
||||
|
||||
$dataClasses = array();
|
||||
|
||||
if(!$_ALL_CLASSES['parents'][$class]) user_error("ClassInfo::dataClassesFor() no parents for $class", E_USER_WARNING);
|
||||
foreach($_ALL_CLASSES['parents'][$class] as $subclass) {
|
||||
if(self::hasTable($subclass)) $dataClasses[] = $subclass;
|
||||
}
|
||||
|
||||
if(self::hasTable($class)) $dataClasses[] = $class;
|
||||
public static function dataClassesFor($class) {
|
||||
$result = array();
|
||||
|
||||
if(isset($_ALL_CLASSES['children'][$class]))
|
||||
foreach($_ALL_CLASSES['children'][$class] as $subclass)
|
||||
{
|
||||
if(self::hasTable($subclass)) $dataClasses[] = $subclass;
|
||||
if (is_object($class)) {
|
||||
$class = get_class($class);
|
||||
}
|
||||
|
||||
return $dataClasses;
|
||||
|
||||
$classes = array_merge(
|
||||
self::ancestry($class),
|
||||
self::subclassesFor($class));
|
||||
|
||||
foreach ($classes as $class) {
|
||||
if (self::hasTable($class)) $result[$class] = $class;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the root data class for that class.
|
||||
* This root table has a lot of special use in the DataObject system.
|
||||
*
|
||||
* @param mixed $class string of the classname or instance of the class
|
||||
* @return array
|
||||
* Returns the root class (the first to extend from DataObject) for the
|
||||
* passed class.
|
||||
*
|
||||
* @param string|object $class
|
||||
* @return string
|
||||
*/
|
||||
static function baseDataClass($class) {
|
||||
global $_ALL_CLASSES;
|
||||
public static function baseDataClass($class) {
|
||||
if (is_object($class)) $class = get_class($class);
|
||||
reset($_ALL_CLASSES['parents'][$class]);
|
||||
while($val = next($_ALL_CLASSES['parents'][$class])) {
|
||||
if($val == 'DataObject') break;
|
||||
|
||||
if (!self::is_subclass_of($class, 'DataObject')) {
|
||||
throw new Exception("$class is not a subclass of DataObject");
|
||||
}
|
||||
|
||||
while ($next = get_parent_class($class)) {
|
||||
if ($next == 'DataObject') {
|
||||
return $class;
|
||||
}
|
||||
|
||||
$class = $next;
|
||||
}
|
||||
$baseDataClass = next($_ALL_CLASSES['parents'][$class]);
|
||||
return $baseDataClass ? $baseDataClass : $class;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a list of classes that inherit from the given class.
|
||||
* The resulting array includes the base class passed
|
||||
@ -125,38 +123,43 @@ class ClassInfo {
|
||||
* @param mixed $class string of the classname or instance of the class
|
||||
* @return array Names of all subclasses as an associative array.
|
||||
*/
|
||||
static function subclassesFor($class){
|
||||
global $_ALL_CLASSES;
|
||||
if (is_object($class)) $class = get_class($class);
|
||||
|
||||
// get all classes from the manifest
|
||||
$subclasses = isset($_ALL_CLASSES['children'][$class]) ? $_ALL_CLASSES['children'][$class] : null;
|
||||
public static function subclassesFor($class) {
|
||||
$descendants = SS_ClassLoader::instance()->getManifest()->getDescendantsOf($class);
|
||||
$result = array($class => $class);
|
||||
|
||||
// add the base class to the array
|
||||
if(isset($subclasses)) {
|
||||
array_unshift($subclasses, $class);
|
||||
if ($descendants) {
|
||||
return $result + ArrayLib::valuekey($descendants);
|
||||
} else {
|
||||
$subclasses[$class] = $class;
|
||||
return $result;
|
||||
}
|
||||
|
||||
return $subclasses;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @todo Improve documentation
|
||||
* Returns the passed class name along with all its parent class names in an
|
||||
* array, sorted with the root class first.
|
||||
*
|
||||
* @param string $class
|
||||
* @param bool $tablesOnly Only return classes that have a table in the db.
|
||||
* @return array
|
||||
*/
|
||||
static function ancestry($class, $onlyWithTables = false) {
|
||||
global $_ALL_CLASSES;
|
||||
public static function ancestry($class, $tablesOnly = false) {
|
||||
$ancestry = array();
|
||||
|
||||
if(is_object($class)) $class = $class->class;
|
||||
else if(!is_string($class)) user_error("Bad class value " . var_export($class, true) . " passed to ClassInfo::ancestry()", E_USER_WARNING);
|
||||
|
||||
$items = $_ALL_CLASSES['parents'][$class];
|
||||
$items[$class] = $class;
|
||||
if($onlyWithTables) foreach($items as $item) {
|
||||
if(!DataObject::has_own_table($item)) unset($items[$item]);
|
||||
if (is_object($class)) {
|
||||
$class = get_class($class);
|
||||
} elseif (!is_string($class)) {
|
||||
throw new Exception(sprintf(
|
||||
'Invalid class value %s, must be an object or string', var_export($class, true)
|
||||
));
|
||||
}
|
||||
return $items;
|
||||
|
||||
do {
|
||||
if (!$tablesOnly || DataObject::has_own_table($class)) {
|
||||
$ancestry[$class] = $class;
|
||||
}
|
||||
} while ($class = get_parent_class($class));
|
||||
|
||||
return array_reverse($ancestry);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -164,27 +167,23 @@ class ClassInfo {
|
||||
* classes and not built-in PHP classes.
|
||||
*/
|
||||
static function implementorsOf($interfaceName) {
|
||||
global $_ALL_CLASSES;
|
||||
return (isset($_ALL_CLASSES['implementors'][$interfaceName])) ? $_ALL_CLASSES['implementors'][$interfaceName] : false;
|
||||
return SS_ClassLoader::instance()->getManifest()->getImplementorsOf($interfaceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given class implements the given interface
|
||||
*/
|
||||
static function classImplements($className, $interfaceName) {
|
||||
global $_ALL_CLASSES;
|
||||
return isset($_ALL_CLASSES['implementors'][$interfaceName][$className]);
|
||||
return in_array($className, SS_ClassLoader::instance()->getManifest()->getImplementorsOf($interfaceName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if $subclass is a subclass of $parentClass.
|
||||
* Identical to the PHP built-in function, but faster.
|
||||
* @deprecated 3.0 Please use is_subclass_of.
|
||||
*/
|
||||
static function is_subclass_of($subclass, $parentClass) {
|
||||
global $_ALL_CLASSES;
|
||||
return isset($_ALL_CLASSES['parents'][$subclass][$parentClass]);
|
||||
public static function is_subclass_of($class, $parent) {
|
||||
return is_subclass_of($class, $parent);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all classes contained in a file.
|
||||
* @uses ManifestBuilder
|
||||
@ -196,11 +195,11 @@ class ClassInfo {
|
||||
* @return array
|
||||
*/
|
||||
static function classes_for_file($filePath) {
|
||||
$absFilePath = Director::getAbsFile($filePath);
|
||||
global $_CLASS_MANIFEST;
|
||||
|
||||
$absFilePath = Director::getAbsFile($filePath);
|
||||
$matchedClasses = array();
|
||||
foreach($_CLASS_MANIFEST as $class => $compareFilePath) {
|
||||
$manifest = SS_ClassLoader::instance()->getManifest()->getClasses();
|
||||
|
||||
foreach($manifest as $class => $compareFilePath) {
|
||||
if($absFilePath == $compareFilePath) $matchedClasses[] = $class;
|
||||
}
|
||||
|
||||
@ -217,11 +216,11 @@ class ClassInfo {
|
||||
* @return array Array of class names
|
||||
*/
|
||||
static function classes_for_folder($folderPath) {
|
||||
$absFolderPath = Director::getAbsFile($folderPath);
|
||||
global $_CLASS_MANIFEST;
|
||||
|
||||
$absFolderPath = Director::getAbsFile($folderPath);
|
||||
$matchedClasses = array();
|
||||
foreach($_CLASS_MANIFEST as $class => $compareFilePath) {
|
||||
$manifest = SS_ClassLoader::instance()->getManifest()->getClasses();
|
||||
|
||||
foreach($manifest as $class => $compareFilePath) {
|
||||
if(stripos($compareFilePath, $absFolderPath) === 0) $matchedClasses[] = $class;
|
||||
}
|
||||
|
||||
|
71
core/Core.php
Executable file → Normal file
71
core/Core.php
Executable file → Normal file
@ -199,44 +199,39 @@ set_include_path(BASE_PATH . '/sapphire' . PATH_SEPARATOR
|
||||
. BASE_PATH . '/sapphire/thirdparty' . PATH_SEPARATOR
|
||||
. get_include_path());
|
||||
|
||||
/**
|
||||
* Sapphire class autoloader. Requires the ManifestBuilder to work.
|
||||
* $_CLASS_MANIFEST must have been loaded up by ManifestBuilder for this to successfully load
|
||||
* classes. Classes will be loaded from any PHP file within the application.
|
||||
* If your class contains an underscore, for example, Page_Controller, then the filename is
|
||||
* expected to be the stuff before the underscore. In this case, Page.php.
|
||||
*
|
||||
* Class names are converted to lowercase for lookup to adhere to PHP's case-insensitive
|
||||
* way of dealing with them.
|
||||
*/
|
||||
function sapphire_autoload($className) {
|
||||
global $_CLASS_MANIFEST;
|
||||
$lClassName = strtolower($className);
|
||||
if(isset($_CLASS_MANIFEST[$lClassName])) include_once($_CLASS_MANIFEST[$lClassName]);
|
||||
else if(isset($_CLASS_MANIFEST[$className])) include_once($_CLASS_MANIFEST[$className]);
|
||||
}
|
||||
|
||||
spl_autoload_register('sapphire_autoload');
|
||||
|
||||
require_once("core/ManifestBuilder.php");
|
||||
require_once("core/ClassInfo.php");
|
||||
require_once('core/Object.php');
|
||||
require_once('core/control/Director.php');
|
||||
require_once('filesystem/Filesystem.php');
|
||||
require_once("core/Session.php");
|
||||
// Include the files needed the initial manifest building, as well as any files
|
||||
// that are needed for the boostrap process on every request.
|
||||
require_once 'cache/Cache.php';
|
||||
require_once 'core/Object.php';
|
||||
require_once 'core/ClassInfo.php';
|
||||
require_once 'core/control/Director.php';
|
||||
require_once 'dev/Debug.php';
|
||||
require_once 'filesystem/FileFinder.php';
|
||||
require_once 'manifest/ClassLoader.php';
|
||||
require_once 'manifest/ClassManifest.php';
|
||||
require_once 'manifest/ManifestFileFinder.php';
|
||||
require_once 'manifest/TemplateLoader.php';
|
||||
require_once 'manifest/TemplateManifest.php';
|
||||
require_once 'manifest/TokenisedRegularExpression.php';
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// MANIFEST
|
||||
|
||||
/**
|
||||
* Include the manifest
|
||||
*/
|
||||
ManifestBuilder::include_manifest();
|
||||
// Regenerate the manifest if ?flush is set, or if the database is being built.
|
||||
// The coupling is a hack, but it removes an annoying bug where new classes
|
||||
// referenced in _config.php files can be referenced during the build process.
|
||||
$flush = (isset($_GET['flush']) || isset($_REQUEST['url']) && (
|
||||
$_REQUEST['url'] == 'dev/build' || $_REQUEST['url'] == BASE_URL . '/dev/build'
|
||||
));
|
||||
$manifest = new SS_ClassManifest(BASE_PATH, false, $flush);
|
||||
|
||||
/**
|
||||
* ?debugmanifest=1 hook
|
||||
*/
|
||||
if(isset($_GET['debugmanifest'])) Debug::show(file_get_contents(MANIFEST_FILE));
|
||||
$loader = SS_ClassLoader::instance();
|
||||
$loader->registerAutoloader();
|
||||
$loader->pushManifest($manifest);
|
||||
|
||||
SS_TemplateLoader::instance()->pushManifest(new SS_TemplateManifest(
|
||||
BASE_PATH, false, isset($_GET['flush'])
|
||||
));
|
||||
|
||||
// If this is a dev site, enable php error reporting
|
||||
// This is necessary to force developers to acknowledge and fix
|
||||
@ -316,16 +311,10 @@ function getTempFolder($base = null) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file where that class is stored.
|
||||
*
|
||||
* @param String $className Case-insensitive lookup.
|
||||
* @return String
|
||||
* @deprecated 3.0 Please use {@link SS_ClassManifest::getItemPath()}.
|
||||
*/
|
||||
function getClassFile($className) {
|
||||
global $_CLASS_MANIFEST;
|
||||
$lClassName = strtolower($className);
|
||||
if(isset($_CLASS_MANIFEST[$lClassName])) return $_CLASS_MANIFEST[$lClassName];
|
||||
else if(isset($_CLASS_MANIFEST[$className])) return $_CLASS_MANIFEST[$className];
|
||||
return SS_ClassLoader::instance()->getManifest()->getItemPath($className);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,630 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Define a constant for the name of the manifest file
|
||||
*/
|
||||
if(!defined('MANIFEST_FILE')) define("MANIFEST_FILE", TEMP_FOLDER . "/manifest-" . str_replace('.php','',basename($_SERVER['SCRIPT_FILENAME'])));
|
||||
|
||||
/**
|
||||
* The ManifestBuilder class generates the manifest file and keeps it fresh.
|
||||
*
|
||||
* The manifest file is a PHP include that contains global variables that
|
||||
* represent the collected contents of the application:
|
||||
* - all classes ({@link __autoload()})
|
||||
* - all templates ({@link SSViewer})
|
||||
* - all _config.php files
|
||||
*
|
||||
* Traversing the filesystem to collect this information on everypage.
|
||||
* This information is cached so that it need not be regenerated on every
|
||||
* pageview.
|
||||
*
|
||||
* <b>Autoloading</b>
|
||||
*
|
||||
* Sapphire class autoloader. Requires the ManifestBuilder to work.
|
||||
* $_CLASS_MANIFEST must have been loaded up by ManifestBuilder for this to successfully load classes.
|
||||
* Classes will be loaded from any PHP file within the application. If your class contains an underscore,
|
||||
* for example, Page_Controller, then the filename is expected to be the stuff before the underscore.
|
||||
* In this case, Page.php.
|
||||
*
|
||||
* @see main.php, __autoload(), SSViewer, Requirements::themedCSS()
|
||||
* @package sapphire
|
||||
* @subpackage core
|
||||
*/
|
||||
class ManifestBuilder {
|
||||
|
||||
static $restrict_to_modules = array();
|
||||
static $extendsArray = array();
|
||||
static $classArray = array();
|
||||
static $implementsArray = array();
|
||||
|
||||
/**
|
||||
* @var array $ignore_files Full filenames (without directory-path) which
|
||||
* should be ignored by the manifest.
|
||||
*/
|
||||
public static $ignore_files = array(
|
||||
'main.php',
|
||||
'cli-script.php',
|
||||
'install.php',
|
||||
'index.php',
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array $ignore_folders Foldernames (without path) which
|
||||
* should be ignored by the manifest.
|
||||
*/
|
||||
public static $ignore_folders = array(
|
||||
'mysql',
|
||||
'assets',
|
||||
'shortstat',
|
||||
'HTML',
|
||||
);
|
||||
|
||||
/**
|
||||
* Include the manifest, regenerating it if necessary
|
||||
*/
|
||||
static function include_manifest() {
|
||||
if(isset($_REQUEST['usetestmanifest'])) {
|
||||
self::load_test_manifest();
|
||||
} else {
|
||||
// The dev/build reference is some coupling but it solves an annoying bug
|
||||
if(!file_exists(MANIFEST_FILE) || (filemtime(MANIFEST_FILE) < filemtime(BASE_PATH))
|
||||
|| isset($_GET['flush']) || (isset($_REQUEST['url']) && ($_REQUEST['url'] == 'dev/build'
|
||||
|| $_REQUEST['url'] == BASE_URL . '/dev/build'))) {
|
||||
self::create_manifest_file();
|
||||
}
|
||||
require_once(MANIFEST_FILE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a copy of the manifest with tests/ folders included.
|
||||
* Only loads the ClassInfo and __autoload() globals; this assumes that _config.php files are already included.
|
||||
*/
|
||||
static function load_test_manifest() {
|
||||
$testManifestFile = MANIFEST_FILE . '-test';
|
||||
|
||||
// The dev/build reference is some coupling but it solves an annoying bug
|
||||
if(!file_exists($testManifestFile)
|
||||
|| (filemtime($testManifestFile) < filemtime(BASE_PATH))
|
||||
|| isset($_GET['flush'])) {
|
||||
|
||||
// Build the manifest, including the tests/ folders
|
||||
$manifestInfo = self::get_manifest_info(BASE_PATH);
|
||||
$manifest = self::generate_php_file($manifestInfo);
|
||||
if($fh = fopen($testManifestFile, 'wb')) {
|
||||
fwrite($fh, $manifest);
|
||||
fclose($fh);
|
||||
} else {
|
||||
user_error("Cannot write manifest file! Check permissions of " . MANIFEST_FILE, E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
require($testManifestFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all PHP class files - actually opening them and executing them.
|
||||
*/
|
||||
static function load_all_classes() {
|
||||
global $_CLASS_MANIFEST;
|
||||
foreach($_CLASS_MANIFEST as $classFile) require_once($classFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new manifest file and saves it to {@link MANIFEST_FILE}.
|
||||
*/
|
||||
static function create_manifest_file() {
|
||||
// Build the manifest, ignoring the tests/ folders
|
||||
$manifestInfo = self::get_manifest_info(BASE_PATH, array("tests"));
|
||||
|
||||
$manifest = self::generate_php_file($manifestInfo);
|
||||
if($fh = fopen(MANIFEST_FILE, 'wb')) {
|
||||
fwrite($fh, $manifest);
|
||||
fclose($fh);
|
||||
} else {
|
||||
user_error("Cannot write manifest file! Check permissions of " . MANIFEST_FILE, E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn an array produced by get_manifest_info() into the content of the manifest PHP include
|
||||
*/
|
||||
static function generate_php_file($manifestInfo) {
|
||||
$output = "<?php\n";
|
||||
|
||||
foreach($manifestInfo['globals'] as $globalName => $globalVal) {
|
||||
$output .= "global \$$globalName;\n\$$globalName = " . var_export($globalVal, true) . ";\n\n";
|
||||
}
|
||||
foreach($manifestInfo['require_once'] as $requireItem) {
|
||||
$output .= 'require_once("' . addslashes($requireItem) . "\");\n";
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the $manifestInfo array, updating the appropriate globals and loading the appropriate _config files.
|
||||
*/
|
||||
static function process_manifest($manifestInfo) {
|
||||
foreach($manifestInfo['globals'] as $globalName => $globalVal) {
|
||||
global $$globalName;
|
||||
$$globalName = $globalVal;
|
||||
}
|
||||
foreach($manifestInfo['require_once'] as $requireItem) {
|
||||
require_once("$requireItem");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get themes from a particular directory.
|
||||
*
|
||||
* @param string $baseDir Optional: Absolute path to theme directory for testing e.g. "/Users/sharvey/Sites/test24/themes"
|
||||
* @param boolean $includeSubThemes If set to TRUE, sub-themes such as "blackcandy_blog" are included too
|
||||
* @return array Listing of theme directories
|
||||
*/
|
||||
public static function get_themes($baseDir = null, $includeSubThemes = false) {
|
||||
// If no base directory specified, the default is the project root
|
||||
if(!$baseDir) $baseDir = BASE_PATH . DIRECTORY_SEPARATOR . THEMES_DIR;
|
||||
$themes = array();
|
||||
if(!file_exists($baseDir)) return $themes;
|
||||
|
||||
$handle = opendir($baseDir);
|
||||
if($handle) {
|
||||
while(false !== ($file = readdir($handle))) {
|
||||
$fullPath = $baseDir . DIRECTORY_SEPARATOR . $file;
|
||||
if(strpos($file, '.') === false && is_dir($fullPath)) {
|
||||
$include = $includeSubThemes ? true : false;
|
||||
if(strpos($file, '_') === false) {
|
||||
$include = true;
|
||||
}
|
||||
if($include) $themes[$file] = $file;
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
}
|
||||
return $themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array containing information for the manifest
|
||||
* @param $baseDir The root directory to analyse
|
||||
* @param $excludedFolders An array folder names to exclude. These don't care about where the
|
||||
* folder appears in the hierarchy, so be careful
|
||||
*/
|
||||
static function get_manifest_info($baseDir, $excludedFolders = array()) {
|
||||
// locate and include the exclude files
|
||||
$topLevel = scandir($baseDir);
|
||||
foreach($topLevel as $file) {
|
||||
if($file[0] == '.') continue
|
||||
|
||||
$fullPath = '';
|
||||
$fullPath = $baseDir . '/' . $file;
|
||||
|
||||
if(@is_dir($fullPath . '/') && file_exists($fullPath . '/_exclude.php')) {
|
||||
require_once($fullPath . '/_exclude.php');
|
||||
}
|
||||
}
|
||||
|
||||
// Project - used to give precedence to template files
|
||||
$project = null;
|
||||
|
||||
// Class, CSS, template manifest
|
||||
$allPhpFiles = array();
|
||||
$templateManifest = array();
|
||||
$cssManifest = array();
|
||||
|
||||
|
||||
if(is_array(self::$restrict_to_modules) && count(self::$restrict_to_modules)) {
|
||||
// $restrict_to_modules is set, so we include only those specified
|
||||
// modules
|
||||
foreach(self::$restrict_to_modules as $module)
|
||||
ManifestBuilder::get_all_php_files($baseDir . '/' . $module, $excludedFolders, $allPhpFiles);
|
||||
} else {
|
||||
// Include all directories which have an _config.php file but don't
|
||||
// have an _manifest_exclude file
|
||||
$topLevel = scandir($baseDir);
|
||||
foreach($topLevel as $filename) {
|
||||
|
||||
// Skip certain directories
|
||||
if($filename[0] == '.') continue;
|
||||
if($filename == THEMES_DIR) continue;
|
||||
if($filename == ASSETS_DIR) continue;
|
||||
if(in_array($filename, $excludedFolders)) continue;
|
||||
|
||||
if(@is_dir("$baseDir/$filename") &&
|
||||
file_exists("$baseDir/$filename/_config.php") &&
|
||||
!file_exists("$baseDir/$filename/_manifest_exclude")) {
|
||||
|
||||
// Get classes, templates, and CSS files
|
||||
ManifestBuilder::get_all_php_files("$baseDir/$filename", $excludedFolders, $allPhpFiles);
|
||||
ManifestBuilder::getTemplateManifest($baseDir, $filename, $excludedFolders, $templateManifest, $cssManifest);
|
||||
|
||||
// List the _config.php files
|
||||
$manifestInfo["require_once"][] = "$baseDir/$filename/_config.php";
|
||||
// Find the $project variable in the relevant config file without having to execute the config file
|
||||
if(preg_match("/\\\$project\s*=\s*[^\n\r]+[\n\r]/", file_get_contents("$baseDir/$filename/_config.php"), $parts)) {
|
||||
eval($parts[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get themes
|
||||
if(file_exists("$baseDir/themes")) {
|
||||
$themeDirs = self::get_themes("$baseDir/themes", true);
|
||||
foreach($themeDirs as $themeDir) {
|
||||
$themeName = strtok($themeDir, '_');
|
||||
ManifestBuilder::getTemplateManifest($baseDir, THEMES_DIR . "/$themeDir", $excludedFolders, $templateManifest, $cssManifest, $themeName);
|
||||
}
|
||||
}
|
||||
|
||||
// Build class-info array from class manifest
|
||||
$allClasses = ManifestBuilder::allClasses($allPhpFiles);
|
||||
|
||||
// Pull the class filenames out
|
||||
$classManifest = $allClasses['file'];
|
||||
unset($allClasses['file']);
|
||||
|
||||
// Ensure that any custom templates get favoured
|
||||
if(!$project) user_error("\$project isn't set", E_USER_WARNING);
|
||||
else if(!file_exists("$baseDir/$project")) user_error("\$project is set to '$project' but no such folder exists.", E_USER_WARNING);
|
||||
else ManifestBuilder::getTemplateManifest($baseDir, $project, $excludedFolders, $templateManifest, $cssManifest);
|
||||
|
||||
$manifestInfo["globals"]["_CLASS_MANIFEST"] = $classManifest;
|
||||
$manifestInfo["globals"]["_ALL_CLASSES"] = $allClasses;
|
||||
$manifestInfo["globals"]["_TEMPLATE_MANIFEST"] = $templateManifest;
|
||||
$manifestInfo["globals"]["_CSS_MANIFEST"] = $cssManifest;
|
||||
|
||||
return $manifestInfo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a list of all the PHP files that should be analysed by the manifest builder.
|
||||
*
|
||||
* @param string $folder The folder to traverse (recursively)
|
||||
* @param array $classMap The already built class map
|
||||
*/
|
||||
private static function get_all_php_files($folder, $excludedFolders, &$allPhpFiles) {
|
||||
$items = scandir($folder);
|
||||
if($items) foreach($items as $item) {
|
||||
// Skip some specific PHP files
|
||||
if(in_array($item, self::$ignore_files)) continue;
|
||||
|
||||
// ignore hidden files and folders
|
||||
if(substr($item,0,1) == '.') continue;
|
||||
|
||||
// ignore files without php-extension
|
||||
if(substr($item,-4) != '.php' && !@is_dir("$folder/$item")) continue;
|
||||
|
||||
// ignore files and folders with underscore-prefix
|
||||
if(substr($item,0,1) == '_') continue;
|
||||
|
||||
// ignore certain directories
|
||||
if(@is_dir("$folder/$item") && in_array($item, self::$ignore_folders)) continue;
|
||||
|
||||
// ignore directories with _manifest_exlude file
|
||||
if(@is_dir("$folder/$item") && file_exists("$folder/$item/_manifest_exclude")) continue;
|
||||
|
||||
// i18n: ignore language files (loaded on demand)
|
||||
if($item == 'lang' && @is_dir("$folder/$item") && ereg_replace("/[^/]+/\\.\\.","",$folder.'/..') == Director::baseFolder()) continue;
|
||||
|
||||
if(@is_dir("$folder/$item")) {
|
||||
// Folder exclusion - used to skip over tests/ folders
|
||||
if(in_array($item, $excludedFolders)) continue;
|
||||
|
||||
// recurse into directories (if not in $ignore_folders)
|
||||
ManifestBuilder::get_all_php_files("$folder/$item", $excludedFolders, $allPhpFiles);
|
||||
} else {
|
||||
$allPhpFiles[] = "$folder/$item";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates the template manifest - a list of all the .ss files in the
|
||||
* application.
|
||||
*
|
||||
* See {@link SSViewer} for an overview on the array structure this class creates.
|
||||
*
|
||||
* @param String $baseDir
|
||||
* @param String $folder
|
||||
*/
|
||||
private static function getTemplateManifest($baseDir, $folder, $excludedFolders, &$templateManifest, &$cssManifest, $themeName = null) {
|
||||
$items = scandir("$baseDir/$folder");
|
||||
if($items) foreach($items as $item) {
|
||||
// Skip hidden files/folders
|
||||
if(substr($item,0,1) == '.') continue;
|
||||
|
||||
// Parse *.ss files
|
||||
if(substr($item,-3) == '.ss') {
|
||||
// Remove extension from template name
|
||||
$templateName = substr($item, 0, -3);
|
||||
|
||||
// The "type" is effectively a subfolder underneath $folder,
|
||||
// mostly "Includes" or "Layout".
|
||||
$templateType = substr($folder,strrpos($folder,'/')+1);
|
||||
// The parent folder counts as type "main"
|
||||
if($templateType == "templates") $templateType = "main";
|
||||
|
||||
// Write either to theme or to non-themed array
|
||||
if($themeName) {
|
||||
$templateManifest[$templateName]['themes'][$themeName][$templateType] = "$baseDir/$folder/$item";
|
||||
} else {
|
||||
$templateManifest[$templateName][$templateType] = "$baseDir/$folder/$item";
|
||||
}
|
||||
|
||||
} else if(substr($item,-4) == '.css') {
|
||||
$cssName = substr($item, 0, -4);
|
||||
// Debug::message($item);
|
||||
|
||||
if($themeName) {
|
||||
$cssManifest[$cssName]['themes'][$themeName] = "$folder/$item";
|
||||
} else {
|
||||
$cssManifest[$cssName]['unthemed'] = "$folder/$item";
|
||||
}
|
||||
|
||||
|
||||
} else if(@is_dir("$baseDir/$folder/$item")) {
|
||||
// Folder exclusion - used to skip over tests/ folders
|
||||
if(in_array($item, $excludedFolders)) continue;
|
||||
|
||||
ManifestBuilder::getTemplateManifest($baseDir, "$folder/$item", $excludedFolders, $templateManifest, $cssManifest, $themeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Include everything, so that actually *all* classes are available and
|
||||
* build a map of classes and their subclasses
|
||||
*
|
||||
* @param $classManifest An array of all Sapphire classes; keys are class names and values are filenames
|
||||
*
|
||||
* @return array Returns an array that holds all class relevant
|
||||
* information.
|
||||
*/
|
||||
private static function allClasses($classManifest) {
|
||||
self::$classArray = array();
|
||||
self::$extendsArray = array();
|
||||
self::$implementsArray = array();
|
||||
|
||||
// Include everything, so we actually have *all* classes
|
||||
foreach($classManifest as $file) {
|
||||
$b = basename($file);
|
||||
if($b != 'cli-script.php' && $b != 'main.php')
|
||||
self::parse_file($file);
|
||||
}
|
||||
|
||||
$allClasses["parents"] = self::find_parents();
|
||||
$allClasses["children"] = self::find_children();
|
||||
$allClasses["implementors"] = self::$implementsArray;
|
||||
|
||||
foreach(self::$classArray as $class => $info) {
|
||||
$allClasses['exists'][$class] = $class;
|
||||
// Class names are converted to lowercase for lookup to adhere to PHP's case-insensitive
|
||||
// way of dealing with them.
|
||||
$allClasses['file'][strtolower($class)] = $info['file'];
|
||||
}
|
||||
|
||||
// Build a map of classes and their subclasses
|
||||
$_classes = get_declared_classes();
|
||||
|
||||
foreach($_classes as $class) {
|
||||
$allClasses['exists'][$class] = $class;
|
||||
foreach($_classes as $subclass) {
|
||||
if(is_subclass_of($class, $subclass)) $allClasses['parents'][$class][$subclass] = $subclass;
|
||||
if(is_subclass_of($subclass, $class)) $allClasses['children'][$class][$subclass] = $subclass;
|
||||
}
|
||||
}
|
||||
|
||||
return $allClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a php file and adds any class or interface information into self::$classArray
|
||||
*
|
||||
* @param string $filename
|
||||
*/
|
||||
private static function parse_file($filename) {
|
||||
$file = file_get_contents($filename);
|
||||
|
||||
$implements = "";
|
||||
$extends = "";
|
||||
$class="";
|
||||
|
||||
if($file === null) user_error("ManifestBuilder::parse_file(): Couldn't open $filename", E_USER_ERROR);
|
||||
if(!$file) return;
|
||||
|
||||
// We cache the parse results of each file, since only a few files will have changed between flushings
|
||||
// And, although it's accurate, TokenisedRegularExpression isn't particularly fast.
|
||||
// We use an MD5 of the file as a part of the cache key because using datetime caused problems when users
|
||||
// were upgrading their sites
|
||||
$fileMD5 = md5($file);
|
||||
$parseCacheFile = TEMP_FOLDER . "/manifestClassParse-" . str_replace(array("/", ":", "\\", "."), "_", basename($filename)) . "-$fileMD5";
|
||||
if(file_exists($parseCacheFile)) {
|
||||
include($parseCacheFile);
|
||||
// Check for a bad cache file
|
||||
if(!isset($classes) || !isset($interfaces) || !is_array($classes) || !is_array($interfaces)) {
|
||||
unset($classes);
|
||||
unset($interfaces);
|
||||
}
|
||||
}
|
||||
|
||||
// Either the parseCacheFile doesn't exist, or its bad
|
||||
if(!isset($classes)) {
|
||||
$tokens = token_get_all($file);
|
||||
$classes = (array)self::getClassDefParser()->findAll($tokens);
|
||||
$interfaces = (array)self::getInterfaceDefParser()->findAll($tokens);
|
||||
|
||||
$cacheContent = '<?php
|
||||
$classes = ' . var_export($classes,true) . ';
|
||||
$interfaces = ' . var_export($interfaces,true) . ';';
|
||||
|
||||
if($fh = fopen($parseCacheFile, 'wb')) {
|
||||
fwrite($fh, $cacheContent);
|
||||
fclose($fh);
|
||||
}
|
||||
}
|
||||
|
||||
foreach($classes as $class) {
|
||||
$className = $class['className'];
|
||||
unset($class['className']);
|
||||
$class['file'] = $filename;
|
||||
if(!isset($class['extends'])) $class['extends'] = null;
|
||||
|
||||
if($class['extends']) self::$extendsArray[$class['extends']][$className] = $className;
|
||||
if(isset($class['interfaces'])) foreach($class['interfaces'] as $interface) {
|
||||
self::$implementsArray[$interface][$className] = $className;
|
||||
}
|
||||
|
||||
if(isset(self::$classArray[$className])) {
|
||||
$file1 = self::$classArray[$className]['file'];
|
||||
$file2 = $class['file'];
|
||||
user_error("There are two files both containing the same class: '$file1' and " .
|
||||
"'$file2'. This might mean that the wrong code is being used.", E_USER_WARNING);
|
||||
}
|
||||
|
||||
self::$classArray[$className] = $class;
|
||||
}
|
||||
|
||||
foreach($interfaces as $interface) {
|
||||
$className = $interface['interfaceName'];
|
||||
unset($interface['interfaceName']);
|
||||
$interface['file'] = $filename;
|
||||
if(!isset($interface['extends'])) $interface['extends'] = null;
|
||||
|
||||
if(isset(self::$classArray[$className])) {
|
||||
$file1 = self::$classArray[$className]['file'];
|
||||
$file2 = $interface[$className];
|
||||
user_error("There are two files both containing the same class: '$file1' and " .
|
||||
"'$file2'. This might mean that the wrong code is being used.", E_USER_WARNING);
|
||||
}
|
||||
|
||||
self::$classArray[$className] = $interface;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link TokenisedRegularExpression} object that will parse class definitions
|
||||
* @return TokenisedRegularExpression
|
||||
*/
|
||||
public static function getClassDefParser() {
|
||||
require_once('core/TokenisedRegularExpression.php');
|
||||
|
||||
return new TokenisedRegularExpression(array(
|
||||
0 => T_CLASS,
|
||||
1 => T_WHITESPACE,
|
||||
2 => array(T_STRING, 'can_jump_to' => array(7, 14), 'save_to' => 'className'),
|
||||
3 => T_WHITESPACE,
|
||||
4 => T_EXTENDS,
|
||||
5 => T_WHITESPACE,
|
||||
6 => array(T_STRING, 'save_to' => 'extends', 'can_jump_to' => 14),
|
||||
7 => T_WHITESPACE,
|
||||
8 => T_IMPLEMENTS,
|
||||
9 => T_WHITESPACE,
|
||||
10 => array(T_STRING, 'can_jump_to' => 14, 'save_to' => 'interfaces[]'),
|
||||
11 => array(T_WHITESPACE, 'optional' => true),
|
||||
12 => array(',', 'can_jump_to' => 10),
|
||||
13 => array(T_WHITESPACE, 'can_jump_to' => 10),
|
||||
14 => array(T_WHITESPACE, 'optional' => true),
|
||||
15 => '{',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link TokenisedRegularExpression} object that will parse class definitions
|
||||
* @return TokenisedRegularExpression
|
||||
*/
|
||||
public static function getInterfaceDefParser() {
|
||||
require_once('core/TokenisedRegularExpression.php');
|
||||
|
||||
return new TokenisedRegularExpression(array(
|
||||
0 => T_INTERFACE,
|
||||
1 => T_WHITESPACE,
|
||||
2 => array(T_STRING, 'can_jump_to' => 7, 'save_to' => 'interfaceName'),
|
||||
3 => T_WHITESPACE,
|
||||
4 => T_EXTENDS,
|
||||
5 => T_WHITESPACE,
|
||||
6 => array(T_STRING, 'save_to' => 'extends'),
|
||||
7 => array(T_WHITESPACE, 'optional' => true),
|
||||
8 => '{',
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Moves through self::$classArray and creates an array containing parent data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function find_parents() {
|
||||
$parentArray = array();
|
||||
foreach(self::$classArray as $class => $info) {
|
||||
$extendArray = array();
|
||||
|
||||
$parent = $info["extends"];
|
||||
|
||||
while($parent) {
|
||||
$extendArray[$parent] = $parent;
|
||||
$parent = isset(self::$classArray[$parent]["extends"]) ? self::$classArray[$parent]["extends"] : null;
|
||||
}
|
||||
$parentArray[$class] = array_reverse($extendArray);
|
||||
}
|
||||
return $parentArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through self::$classArray and returns an array with any descendant data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function find_children() {
|
||||
$childrenArray = array();
|
||||
foreach(self::$extendsArray as $class => $children) {
|
||||
$allChildren = $children;
|
||||
foreach($children as $childName) {
|
||||
$allChildren = array_merge($allChildren, self::up_children($childName));
|
||||
}
|
||||
$childrenArray[$class] = $allChildren;
|
||||
}
|
||||
return $childrenArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to find all children of give class
|
||||
*
|
||||
* @param string $class
|
||||
* @return array
|
||||
*/
|
||||
private static function get_children($class) {
|
||||
return isset(self::$extendsArray[$class]) ? self::$extendsArray[$class] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the Manifest has been included
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
static function has_been_included() {
|
||||
global $_CLASS_MANIFEST, $_TEMPLATE_MANIFEST, $_CSS_MANIFEST, $_ALL_CLASSES;
|
||||
return (bool)!(empty($_CLASS_MANIFEST) && empty($_TEMPLATE_MANIFEST) && empty($_CSS_MANIFEST) && empty($_ALL_CLASSES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a flat array with all children of a given class
|
||||
*
|
||||
* @param string $class
|
||||
* @param array $results
|
||||
*/
|
||||
static function up_children($class) {
|
||||
$children = self::get_Children($class);
|
||||
$results = $children;
|
||||
foreach($children as $className) {
|
||||
$results = array_merge($results, self::up_children($className));
|
||||
}
|
||||
return $results;;
|
||||
}
|
||||
}
|
4
core/Object.php
Executable file → Normal file
4
core/Object.php
Executable file → Normal file
@ -513,7 +513,7 @@ abstract class Object {
|
||||
user_error(sprintf('Object::add_extension() - Can\'t find extension class for "%s"', $extensionClass), E_USER_ERROR);
|
||||
}
|
||||
|
||||
if(!ClassInfo::is_subclass_of($extensionClass, 'Extension')) {
|
||||
if(!is_subclass_of($extensionClass, 'Extension')) {
|
||||
user_error(sprintf('Object::add_extension() - Extension "%s" is not a subclass of Extension', $extensionClass), E_USER_ERROR);
|
||||
}
|
||||
|
||||
@ -537,7 +537,7 @@ abstract class Object {
|
||||
self::set_static($class, 'extensions', $extensions);
|
||||
|
||||
// load statics now for DataObject classes
|
||||
if(ClassInfo::is_subclass_of($class, 'DataObject')) {
|
||||
if(is_subclass_of($class, 'DataObject')) {
|
||||
if(is_subclass_of($extensionClass, 'DataObjectDecorator')) {
|
||||
DataObjectDecorator::load_extra_statics($class, $extension);
|
||||
}
|
||||
|
@ -143,15 +143,24 @@ class Requirements {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given "themeable stylesheet" as required. See {@link Requirements_Backend::themedCSS()}
|
||||
*
|
||||
* @param $name String The identifier of the file. For example, css/MyFile.css would have the identifier "MyFile"
|
||||
* @param $media String Comma-separated list of media-types (e.g. "screen,projector")
|
||||
* Registers the given themeable stylesheet as required.
|
||||
*
|
||||
* A CSS file in the current theme path name "themename/css/$name.css" is
|
||||
* first searched for, and it that doesn't exist and the module parameter is
|
||||
* set then a CSS file with that name in the module is used.
|
||||
*
|
||||
* NOTE: This API is experimental and may change in the future.
|
||||
*
|
||||
* @param string $name The name of the file - e.g. "/css/File.css" would have
|
||||
* the name "File".
|
||||
* @param string $module The module to fall back to if the css file does not
|
||||
* exist in the current theme.
|
||||
* @param string $media The CSS media attribute.
|
||||
*/
|
||||
static function themedCSS($name, $media = null) {
|
||||
return self::backend()->themedCSS($name, $media);
|
||||
public static function themedCSS($name, $module = null, $media = null) {
|
||||
return self::backend()->themedCSS($name, $module, $media);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear either a single or all requirements.
|
||||
* Caution: Clearing single rules works only with customCSS and customScript if you specified a {@uniquenessID}.
|
||||
@ -1044,27 +1053,22 @@ class Requirements_Backend {
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given "themeable stylesheet" as required.
|
||||
* Themeable stylesheets have globally unique names, just like templates and PHP files.
|
||||
* Because of this, they can be replaced by similarly named CSS files in the theme directory.
|
||||
*
|
||||
* @param $name String The identifier of the file. For example, css/MyFile.css would have the identifier "MyFile"
|
||||
* @param $media String Comma-separated list of media-types (e.g. "screen,projector")
|
||||
*/
|
||||
function themedCSS($name, $media = null) {
|
||||
global $_CSS_MANIFEST;
|
||||
|
||||
$theme = SSViewer::current_theme();
|
||||
|
||||
if($theme && isset($_CSS_MANIFEST[$name]) && isset($_CSS_MANIFEST[$name]['themes'])
|
||||
&& isset($_CSS_MANIFEST[$name]['themes'][$theme]))
|
||||
$this->css($_CSS_MANIFEST[$name]['themes'][$theme], $media);
|
||||
|
||||
else if(isset($_CSS_MANIFEST[$name]) && isset($_CSS_MANIFEST[$name]['unthemed'])) $this->css($_CSS_MANIFEST[$name]['unthemed'], $media);
|
||||
// Normal requirements fails quietly when there is no css - we should do the same
|
||||
// else user_error("themedCSS - No CSS file '$name.css' found.", E_USER_WARNING);
|
||||
/**
|
||||
* @see Requirements::themedCSS()
|
||||
*/
|
||||
public function themedCSS($name, $module = null, $media = null) {
|
||||
$theme = SSViewer::current_theme();
|
||||
$path = SSViewer::get_theme_folder() . "/css/$name.css";
|
||||
|
||||
if (file_exists(BASE_PATH . '/' . $path)) {
|
||||
$this->css($path, $media);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($module) {
|
||||
$this->css("$module/css/$name.css");
|
||||
}
|
||||
}
|
||||
|
||||
function debug() {
|
||||
|
188
core/SSViewer.php
Executable file → Normal file
188
core/SSViewer.php
Executable file → Normal file
@ -182,46 +182,10 @@ class SSViewer_DataPresenter extends SSViewer_Scope {
|
||||
*
|
||||
* Compiled templates are cached via {@link SS_Cache}, usually on the filesystem.
|
||||
* If you put ?flush=all on your URL, it will force the template to be recompiled.
|
||||
*
|
||||
* <b>Manifest File and Structure</b>
|
||||
*
|
||||
* Works with the global $_TEMPLATE_MANIFEST which is compiled by {@link ManifestBuilder->getTemplateManifest()}.
|
||||
* This associative array lists all template filepaths by "identifier", meaning the name
|
||||
* of the template without its path or extension.
|
||||
*
|
||||
* Example:
|
||||
* <code>
|
||||
* array(
|
||||
* 'LeftAndMain' =>
|
||||
* array (
|
||||
* 'main' => '/my/system/path/cms/templates/LeftAndMain.ss',
|
||||
* ),
|
||||
* 'CMSMain_left' =>
|
||||
* array (
|
||||
* 'Includes' => '/my/system/path/cms/templates/Includes/CMSMain_left.ss',
|
||||
* ),
|
||||
* 'Page' =>
|
||||
* array (
|
||||
* 'themes' =>
|
||||
* array (
|
||||
* 'blackcandy' =>
|
||||
* array (
|
||||
* 'Layout' => '/my/system/path/themes/blackcandy/templates/Layout/Page.ss',
|
||||
* 'main' => '/my/system/path/themes/blackcandy/templates/Page.ss',
|
||||
* ),
|
||||
* 'blue' =>
|
||||
* array (
|
||||
* 'Layout' => '/my/system/path/themes/mysite/templates/Layout/Page.ss',
|
||||
* 'main' => '/my/system/path/themes/mysite/templates/Page.ss',
|
||||
* ),
|
||||
* ),
|
||||
* ),
|
||||
* // ...
|
||||
* )
|
||||
* </code>
|
||||
*
|
||||
* @see http://doc.silverstripe.org/themes
|
||||
* @see http://doc.silverstripe.org/themes:developing
|
||||
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage view
|
||||
@ -306,6 +270,30 @@ class SSViewer {
|
||||
return self::current_theme() ? THEMES_DIR . "/" . self::current_theme() : project();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of theme names present in a directory.
|
||||
*
|
||||
* @param string $path
|
||||
* @param bool $subthemes Include subthemes (default false).
|
||||
* @return array
|
||||
*/
|
||||
public static function get_themes($path = null, $subthemes = false) {
|
||||
$path = rtrim($path ? $path : THEMES_PATH, '/');
|
||||
$themes = array();
|
||||
|
||||
if (!is_dir($path)) return $themes;
|
||||
|
||||
foreach (scandir($path) as $item) {
|
||||
if ($item[0] != '.' && is_dir("$path/$item")) {
|
||||
if ($subthemes || !strpos($item, '_')) {
|
||||
$themes[$item] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@ -322,8 +310,6 @@ class SSViewer {
|
||||
* </code>
|
||||
*/
|
||||
public function __construct($templateList) {
|
||||
global $_TEMPLATE_MANIFEST;
|
||||
|
||||
// flush template manifest cache if requested
|
||||
if (isset($_GET['flush']) && $_GET['flush'] == 'all') {
|
||||
if(Director::isDev() || Director::is_cli() || Permission::check('ADMIN')) {
|
||||
@ -336,64 +322,25 @@ class SSViewer {
|
||||
if(substr((string) $templateList,-3) == '.ss') {
|
||||
$this->chosenTemplates['main'] = $templateList;
|
||||
} else {
|
||||
if(!is_array($templateList)) $templateList = array($templateList);
|
||||
|
||||
if(isset($_GET['debug_request'])) Debug::message("Selecting templates from the following list: " . implode(", ", $templateList));
|
||||
|
||||
foreach($templateList as $template) {
|
||||
// if passed as a partial directory (e.g. "Layout/Page"), split into folder and template components
|
||||
if(strpos($template,'/') !== false) list($templateFolder, $template) = explode('/', $template, 2);
|
||||
else $templateFolder = null;
|
||||
|
||||
// Use the theme template if available
|
||||
if(self::current_theme() && isset($_TEMPLATE_MANIFEST[$template]['themes'][self::current_theme()])) {
|
||||
$this->chosenTemplates = array_merge(
|
||||
$_TEMPLATE_MANIFEST[$template]['themes'][self::current_theme()],
|
||||
$this->chosenTemplates
|
||||
);
|
||||
|
||||
if(isset($_GET['debug_request'])) Debug::message("Found template '$template' from main theme '" . self::current_theme() . "': " . var_export($_TEMPLATE_MANIFEST[$template]['themes'][self::current_theme()], true));
|
||||
}
|
||||
|
||||
// Fall back to unthemed base templates
|
||||
if(isset($_TEMPLATE_MANIFEST[$template]) && (array_keys($_TEMPLATE_MANIFEST[$template]) != array('themes'))) {
|
||||
$this->chosenTemplates = array_merge(
|
||||
$_TEMPLATE_MANIFEST[$template],
|
||||
$this->chosenTemplates
|
||||
);
|
||||
|
||||
if(isset($_GET['debug_request'])) Debug::message("Found template '$template' from main template archive, containing the following items: " . var_export($_TEMPLATE_MANIFEST[$template], true));
|
||||
|
||||
unset($this->chosenTemplates['themes']);
|
||||
}
|
||||
|
||||
if($templateFolder) {
|
||||
$this->chosenTemplates['main'] = $this->chosenTemplates[$templateFolder];
|
||||
unset($this->chosenTemplates[$templateFolder]);
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($_GET['debug_request'])) Debug::message("Final template selections made: " . var_export($this->chosenTemplates, true));
|
||||
|
||||
$this->chosenTemplates = SS_TemplateLoader::instance()->findTemplates(
|
||||
$templateList, self::current_theme()
|
||||
);
|
||||
}
|
||||
|
||||
if(!$this->chosenTemplates) user_error("None of these templates can be found in theme '"
|
||||
. self::current_theme() . "': ". implode(".ss, ", $templateList) . ".ss", E_USER_WARNING);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if at least one of the listed templates exists
|
||||
*/
|
||||
static function hasTemplate($templateList) {
|
||||
if(!is_array($templateList)) $templateList = array($templateList);
|
||||
|
||||
global $_TEMPLATE_MANIFEST;
|
||||
foreach($templateList as $template) {
|
||||
if(strpos($template,'/') !== false) list($templateFolder, $template) = explode('/', $template, 2);
|
||||
if(isset($_TEMPLATE_MANIFEST[$template])) return true;
|
||||
public static function hasTemplate($templates) {
|
||||
$manifest = SS_TemplateLoader::instance()->getManifest();
|
||||
|
||||
foreach ((array) $templates as $template) {
|
||||
if ($manifest->getTemplate($template)) return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -433,76 +380,21 @@ class SSViewer {
|
||||
public function exists() {
|
||||
return $this->chosenTemplates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a template name in the current theme:
|
||||
* - themes/mytheme/templates
|
||||
* - themes/mytheme/templates/Includes
|
||||
* Falls back to unthemed template files.
|
||||
*
|
||||
* Caution: Doesn't search in any /Layout folders.
|
||||
*
|
||||
* @param string $identifier A template name without '.ss' extension or path.
|
||||
* @return string Full system path to a template file
|
||||
*/
|
||||
public static function getTemplateFile($identifier) {
|
||||
global $_TEMPLATE_MANIFEST;
|
||||
|
||||
$includeTemplateFile = self::getTemplateFileByType($identifier, 'Includes');
|
||||
if($includeTemplateFile) return $includeTemplateFile;
|
||||
|
||||
$mainTemplateFile = self::getTemplateFileByType($identifier, 'main');
|
||||
if($mainTemplateFile) return $mainTemplateFile;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $identifier A template name without '.ss' extension or path
|
||||
* @param string $type The template type, either "main", "Includes" or "Layout"
|
||||
* @return string Full system path to a template file
|
||||
*/
|
||||
public static function getTemplateFileByType($identifier, $type) {
|
||||
global $_TEMPLATE_MANIFEST;
|
||||
if(self::current_theme() && isset($_TEMPLATE_MANIFEST[$identifier]['themes'][self::current_theme()][$type])) {
|
||||
return $_TEMPLATE_MANIFEST[$identifier]['themes'][self::current_theme()][$type];
|
||||
} else if(isset($_TEMPLATE_MANIFEST[$identifier][$type])){
|
||||
return $_TEMPLATE_MANIFEST[$identifier][$type];
|
||||
} else {
|
||||
return false;
|
||||
$loader = SS_TemplateLoader::instance();
|
||||
$found = $loader->findTemplates("$type/$identifier", self::current_theme());
|
||||
|
||||
if ($found) {
|
||||
return $found['main'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by <% include Identifier %> statements to get the full
|
||||
* unparsed content of a template file.
|
||||
*
|
||||
* @uses getTemplateFile()
|
||||
* @param string $identifier A template name without '.ss' extension or path.
|
||||
* @return string content of template
|
||||
*/
|
||||
public static function getTemplateContent($identifier) {
|
||||
if(!SSViewer::getTemplateFile($identifier)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$content = file_get_contents(SSViewer::getTemplateFile($identifier));
|
||||
|
||||
// $content = "<!-- getTemplateContent() :: identifier: $identifier -->". $content;
|
||||
// Adds an i18n namespace to all _t(...) calls without an existing one
|
||||
// to avoid confusion when using the include in different contexts.
|
||||
// Entities without a namespace are deprecated, but widely used.
|
||||
$content = ereg_replace('<' . '% +_t\((\'([^\.\']*)\'|"([^\."]*)")(([^)]|\)[^ ]|\) +[^% ])*)\) +%' . '>', '<?= _t(\''. $identifier . '.ss' . '.\\2\\3\'\\4) ?>', $content);
|
||||
|
||||
// Remove UTF-8 byte order mark
|
||||
// This is only necessary if you don't have zend-multibyte enabled.
|
||||
if(substr($content, 0,3) == pack("CCC", 0xef, 0xbb, 0xbf)) {
|
||||
$content = substr($content, 3);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
2
core/control/ContentController.php
Executable file → Normal file
2
core/control/ContentController.php
Executable file → Normal file
@ -230,7 +230,7 @@ class ContentController extends Controller {
|
||||
$hasOnes = $this->dataRecord->has_one();
|
||||
if(!$hasOnes) return false;
|
||||
foreach($hasOnes as $hasOneName => $hasOneClass) {
|
||||
if($hasOneClass == 'WidgetArea' || ClassInfo::is_subclass_of($hasOneClass, 'WidgetArea')) {
|
||||
if($hasOneClass == 'WidgetArea' || is_subclass_of($hasOneClass, 'WidgetArea')) {
|
||||
$widgetAreaRelations[] = $hasOneName;
|
||||
}
|
||||
}
|
||||
|
2
core/control/HTTPRequest.php
Executable file → Normal file
2
core/control/HTTPRequest.php
Executable file → Normal file
@ -348,7 +348,7 @@ class SS_HTTPRequest implements ArrayAccess {
|
||||
if($varRequired && !isset($this->dirParts[$i])) return false;
|
||||
|
||||
$arguments[$varName] = isset($this->dirParts[$i]) ? $this->dirParts[$i] : null;
|
||||
if($part == '$Controller' && (!ClassInfo::exists($arguments['Controller']) || !ClassInfo::is_subclass_of($arguments['Controller'], 'Controller'))) {
|
||||
if($part == '$Controller' && (!ClassInfo::exists($arguments['Controller']) || !is_subclass_of($arguments['Controller'], 'Controller'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
44
core/i18n.php
Executable file → Normal file
44
core/i18n.php
Executable file → Normal file
@ -1680,41 +1680,25 @@ class i18n extends Object {
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a file name (a php class name, without the .php ext, or a template name, including the .ss extension)
|
||||
* this helper function determines the module where this file is located
|
||||
* Given a PHP class name, finds the module where it's located.
|
||||
*
|
||||
* @param string $name php class name or template file name (including *.ss extension)
|
||||
* @return string Module where the file is located
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
public static function get_owner_module($name) {
|
||||
// if $name is a template file
|
||||
if(substr($name,-3) == '.ss') {
|
||||
global $_TEMPLATE_MANIFEST;
|
||||
$templateManifest = $_TEMPLATE_MANIFEST[substr($name,0,-3)];
|
||||
if(is_array($templateManifest) && isset($templateManifest['themes'])) {
|
||||
$absolutePath = $templateManifest['themes'][SSViewer::current_theme()];
|
||||
} else {
|
||||
$absolutePath = $templateManifest;
|
||||
}
|
||||
|
||||
$path = str_replace('\\','/',Director::makeRelative(current($absolutePath)));
|
||||
|
||||
ereg('/([^/]+)/',$path,$module);
|
||||
}
|
||||
// $name is assumed to be a PHP class
|
||||
else {
|
||||
global $_CLASS_MANIFEST;
|
||||
if(strpos($name,'_') !== false) $name = strtok($name,'_');
|
||||
$name = strtolower($name); // Necessary because of r101131
|
||||
if(isset($_CLASS_MANIFEST[$name])) {
|
||||
$path = str_replace('\\','/',Director::makeRelative($_CLASS_MANIFEST[$name]));
|
||||
ereg('/([^/]+)/', $path, $module);
|
||||
}
|
||||
}
|
||||
return (isset($module)) ? $module[1] : false;
|
||||
$manifest = SS_ClassLoader::instance()->getManifest();
|
||||
$path = $manifest->getItemPath($name);
|
||||
|
||||
if (!$path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = Director::makeRelative($path);
|
||||
$path = str_replace('\\', '/', $path);
|
||||
|
||||
return substr($path, 0, strpos($path, '/'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates a "long" locale format (e.g. "en_US")
|
||||
* by checking it against {@link $all_locales}.
|
||||
|
@ -2605,8 +2605,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// Get the tables to join to
|
||||
$tableClasses = ClassInfo::dataClassesFor($this->class);
|
||||
if(!$tableClasses) {
|
||||
if(!ManifestBuilder::has_been_included()) {
|
||||
user_error("DataObjects have been requested before the manifest is loaded. Please ensure you are not querying the database in _config.php.", E_USER_ERROR);
|
||||
if (!DB::getConn()) {
|
||||
throw new Exception('DataObjects have been requested before'
|
||||
. ' a DB connection has been made. Please ensure you'
|
||||
. ' are not querying the database in _config.php.');
|
||||
} else {
|
||||
user_error("DataObject::buildSQL: Can't find data classes (classes linked to tables) for $this->class. Please ensure you run dev/build after creating a new DataObject.", E_USER_ERROR);
|
||||
}
|
||||
|
@ -91,9 +91,8 @@ class DatabaseAdmin extends Controller {
|
||||
increase_time_limit_to(600);
|
||||
|
||||
// Get all our classes
|
||||
ManifestBuilder::create_manifest_file();
|
||||
require(MANIFEST_FILE);
|
||||
|
||||
SS_ClassLoader::instance()->getManifest()->regenerate();
|
||||
|
||||
if(isset($_GET['returnURL'])) {
|
||||
echo "<p>Setting up the database; you will be returned to your site shortly....</p>";
|
||||
$this->doBuild(true);
|
||||
|
@ -116,7 +116,7 @@ class SiteConfig extends DataObject implements PermissionProvider {
|
||||
* @return array of theme directory names
|
||||
*/
|
||||
public function getAvailableThemes($baseDir = null) {
|
||||
$themes = ManifestBuilder::get_themes($baseDir);
|
||||
$themes = SSViewer::get_themes($baseDir);
|
||||
foreach(self::$disabled_themes as $theme) {
|
||||
if(isset($themes[$theme])) unset($themes[$theme]);
|
||||
}
|
||||
|
2
core/model/Versioned.php
Executable file → Normal file
2
core/model/Versioned.php
Executable file → Normal file
@ -479,7 +479,7 @@ class Versioned extends DataObjectDecorator {
|
||||
*/
|
||||
function canBeVersioned($table) {
|
||||
return ClassInfo::exists($table)
|
||||
&& ClassInfo::is_subclass_of($table, 'DataObject')
|
||||
&& is_subclass_of($table, 'DataObject')
|
||||
&& DataObject::has_own_table($table);
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ class CodeViewer extends Controller {
|
||||
parent::init();
|
||||
|
||||
if(!Permission::check('ADMIN')) return Security::permissionFailure();
|
||||
ManifestBuilder::load_test_manifest();
|
||||
TestRunner::use_test_manifest();
|
||||
}
|
||||
|
||||
public function browse() {
|
||||
|
@ -128,11 +128,12 @@ class ModelViewer_Model extends ViewableData {
|
||||
}
|
||||
|
||||
function getModule() {
|
||||
global $_CLASS_MANIFEST;
|
||||
$classes = SS_ClassLoader::instance()->getManifest()->getClasses();
|
||||
$className = strtolower($this->className);
|
||||
|
||||
if(($pos = strpos($className,'_')) !== false) $className = substr($className,0,$pos);
|
||||
if(isset($_CLASS_MANIFEST[$className])) {
|
||||
if(preg_match('/^'.str_replace('/','\/',preg_quote(BASE_PATH)).'\/([^\/]+)\//', $_CLASS_MANIFEST[$className], $matches)) {
|
||||
if(isset($classes[$className])) {
|
||||
if(preg_match('/^'.str_replace('/','\/',preg_quote(BASE_PATH)).'\/([^\/]+)\//', $classes[$className], $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,21 @@ class TestRunner extends Controller {
|
||||
if (is_string($reporter)) $reporter = new $reporter;
|
||||
self::$default_reporter = $reporter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pushes a class and template manifest instance that include tests onto the
|
||||
* top of the loader stacks.
|
||||
*/
|
||||
public static function use_test_manifest() {
|
||||
SS_ClassLoader::instance()->pushManifest(new SS_ClassManifest(
|
||||
BASE_PATH, true, isset($_GET['flush'])
|
||||
));
|
||||
|
||||
SS_TemplateLoader::instance()->pushManifest(new SS_TemplateManifest(
|
||||
BASE_PATH, true, isset($_GET['flush'])
|
||||
));
|
||||
}
|
||||
|
||||
function init() {
|
||||
parent::init();
|
||||
|
||||
@ -102,7 +116,7 @@ class TestRunner extends Controller {
|
||||
* Currently excludes PhpSyntaxTest
|
||||
*/
|
||||
function all($request, $coverage = false) {
|
||||
ManifestBuilder::load_test_manifest();
|
||||
self::use_test_manifest();
|
||||
$tests = ClassInfo::subclassesFor('SapphireTest');
|
||||
array_shift($tests);
|
||||
unset($tests['FunctionalTest']);
|
||||
@ -122,7 +136,7 @@ class TestRunner extends Controller {
|
||||
* Run test classes that should be run before build - i.e., everything possible.
|
||||
*/
|
||||
function build() {
|
||||
ManifestBuilder::load_test_manifest();
|
||||
self::use_test_manifest();
|
||||
$tests = ClassInfo::subclassesFor('SapphireTest');
|
||||
array_shift($tests);
|
||||
unset($tests['FunctionalTest']);
|
||||
@ -138,7 +152,7 @@ class TestRunner extends Controller {
|
||||
* Browse all enabled test cases in the environment
|
||||
*/
|
||||
function browse() {
|
||||
ManifestBuilder::load_test_manifest();
|
||||
self::use_test_manifest();
|
||||
self::$default_reporter->writeHeader();
|
||||
self::$default_reporter->writeInfo('Available Tests', false);
|
||||
if(Director::is_cli()) {
|
||||
@ -170,7 +184,7 @@ class TestRunner extends Controller {
|
||||
* Run a coverage test across all modules
|
||||
*/
|
||||
function coverageAll($request) {
|
||||
ManifestBuilder::load_all_classes();
|
||||
self::use_test_manifest();
|
||||
$this->all($request, true);
|
||||
}
|
||||
|
||||
@ -197,7 +211,7 @@ class TestRunner extends Controller {
|
||||
* Run only a single test class or a comma-separated list of tests
|
||||
*/
|
||||
function only($request, $coverage = false) {
|
||||
ManifestBuilder::load_test_manifest();
|
||||
self::use_test_manifest();
|
||||
if($request->param('TestCase') == 'all') {
|
||||
$this->all();
|
||||
} else {
|
||||
@ -217,7 +231,7 @@ class TestRunner extends Controller {
|
||||
* A module is generally a toplevel folder, e.g. "mysite" or "sapphire".
|
||||
*/
|
||||
function module($request, $coverage = false) {
|
||||
ManifestBuilder::load_test_manifest();
|
||||
self::use_test_manifest();
|
||||
$classNames = array();
|
||||
$moduleNames = explode(',', $request->param('ModuleName'));
|
||||
foreach($moduleNames as $moduleName) {
|
||||
|
222
filesystem/FileFinder.php
Normal file
222
filesystem/FileFinder.php
Normal file
@ -0,0 +1,222 @@
|
||||
<?php
|
||||
/**
|
||||
* A utility class that finds any files matching a set of rules that are
|
||||
* present within a directory tree.
|
||||
*
|
||||
* Each file finder instance can have several options set on it:
|
||||
* - name_regex (string): A regular expression that file basenames must match.
|
||||
* - accept_callback (callback): A callback that is called to accept a file.
|
||||
* If it returns false the item will be skipped. The callback is passed the
|
||||
* basename, pathname and depth.
|
||||
* - accept_dir_callback (callback): The same as accept_callback, but only
|
||||
* called for directories.
|
||||
* - accept_file_callback (callback): The same as accept_callback, but only
|
||||
* called for files.
|
||||
* - file_callback (callback): A callback that is called when a file i
|
||||
* succesfully matched. It is passed the basename, pathname and depth.
|
||||
* - dir_callback (callback): The same as file_callback, but called for
|
||||
* directories.
|
||||
* - ignore_files (array): An array of file names to skip.
|
||||
* - ignore_dirs (array): An array of directory names to skip.
|
||||
* - ignore_vcs (bool): Skip over commonly used VCS dirs (svn, git, hg, bzr).
|
||||
* This is enabled by default. The names of VCS directories to skip over
|
||||
* are defined in {@link SS_FileFInder::$vcs_dirs}.
|
||||
* - max_depth (int): The maxmium depth to traverse down the folder tree,
|
||||
* default to unlimited.
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage filesystem
|
||||
*/
|
||||
class SS_FileFinder {
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $vcs_dirs = array(
|
||||
'.git', '.svn', '.hg', '.bzr'
|
||||
);
|
||||
|
||||
/**
|
||||
* The default options that are set on a new finder instance. Options not
|
||||
* present in this array cannot be set.
|
||||
*
|
||||
* Any default_option statics defined on child classes are also taken into
|
||||
* account.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $default_options = array(
|
||||
'name_regex' => null,
|
||||
'accept_callback' => null,
|
||||
'accept_dir_callback' => null,
|
||||
'accept_file_callback' => null,
|
||||
'file_callback' => null,
|
||||
'dir_callback' => null,
|
||||
'ignore_files' => null,
|
||||
'ignore_dirs' => null,
|
||||
'ignore_vcs' => true,
|
||||
'min_depth' => null,
|
||||
'max_depth' => null
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
public function __construct() {
|
||||
$this->options = Object::combined_static(get_class($this), 'default_options');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an option value set on this instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOption($name) {
|
||||
if (!array_key_exists($name, $this->options)) {
|
||||
throw new InvalidArgumentException("The option $name doesn't exist.");
|
||||
}
|
||||
|
||||
return $this->options[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an option on this finder instance. See {@link SS_FileFinder} for the
|
||||
* list of options available.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function setOption($name, $value) {
|
||||
if (!array_key_exists($name, $this->options)) {
|
||||
throw new InvalidArgumentException("The option $name doesn't exist.");
|
||||
}
|
||||
|
||||
$this->options[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets several options at once.
|
||||
*
|
||||
* @param array $options
|
||||
*/
|
||||
public function setOptions(array $options) {
|
||||
foreach ($options as $k => $v) $this->setOption($k, $v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all files matching the options within a directory. The search is
|
||||
* performed depth first.
|
||||
*
|
||||
* @param string $base
|
||||
* @return array
|
||||
*/
|
||||
public function find($base) {
|
||||
$paths = array(array(rtrim($base, '/'), 0));
|
||||
$found = array();
|
||||
|
||||
$fileCallback = $this->getOption('file_callback');
|
||||
$dirCallback = $this->getOption('dir_callback');
|
||||
|
||||
while ($path = array_shift($paths)) {
|
||||
list($path, $depth) = $path;
|
||||
|
||||
foreach (scandir($path) as $basename) {
|
||||
if ($basename == '.' || $basename == '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_dir("$path/$basename")) {
|
||||
if (!$this->acceptDir($basename, "$path/$basename", $depth + 1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($dirCallback) {
|
||||
call_user_func(
|
||||
$dirCallback, $basename, "$path/$basename", $depth + 1
|
||||
);
|
||||
}
|
||||
|
||||
$paths[] = array("$path/$basename", $depth + 1);
|
||||
} else {
|
||||
if (!$this->acceptFile($basename, "$path/$basename", $depth)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($fileCallback) {
|
||||
call_user_func(
|
||||
$fileCallback, $basename, "$path/$basename", $depth
|
||||
);
|
||||
}
|
||||
|
||||
$found[] = "$path/$basename";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if the directory should be traversed. This can be overloaded
|
||||
* to customise functionality, or extended with callbacks.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function acceptDir($basename, $pathname, $depth) {
|
||||
if ($this->getOption('ignore_vcs') && in_array($basename, self::$vcs_dirs)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($ignore = $this->getOption('ignore_dirs')) {
|
||||
if (in_array($basename, $ignore)) return false;
|
||||
}
|
||||
|
||||
if ($max = $this->getOption('max_depth')) {
|
||||
if ($depth > $max) return false;
|
||||
}
|
||||
|
||||
if ($callback = $this->getOption('accept_callback')) {
|
||||
if (!call_user_func($callback, $basename, $pathname, $depth)) return false;
|
||||
}
|
||||
|
||||
if ($callback = $this->getOption('accept_dir_callback')) {
|
||||
if (!call_user_func($callback, $basename, $pathname, $depth)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if the file should be included in the results. This can be
|
||||
* overloaded to customise functionality, or extended via callbacks.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function acceptFile($basename, $pathname, $depth) {
|
||||
if ($regex = $this->getOption('name_regex')) {
|
||||
if (!preg_match($regex, $basename)) return false;
|
||||
}
|
||||
|
||||
if ($ignore = $this->getOption('ignore_files')) {
|
||||
if (in_array($basename, $ignore)) return false;
|
||||
}
|
||||
|
||||
if ($minDepth = $this->getOption('min_depth')) {
|
||||
if ($depth < $minDepth) return false;
|
||||
}
|
||||
|
||||
if ($callback = $this->getOption('accept_callback')) {
|
||||
if (!call_user_func($callback, $basename, $pathname, $depth)) return false;
|
||||
}
|
||||
|
||||
if ($callback = $this->getOption('accept_file_callback')) {
|
||||
if (!call_user_func($callback, $basename, $pathname, $depth)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
23
forms/ComplexTableField.php
Executable file → Normal file
23
forms/ComplexTableField.php
Executable file → Normal file
@ -937,29 +937,6 @@ class ComplexTableField_ItemRequest extends TableListField_ItemRequest {
|
||||
* #################################
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get part of class ancestry (even if popup is not subclassed it might be styled differently in css)
|
||||
*/
|
||||
function PopupClasses() {
|
||||
global $_ALL_CLASSES;
|
||||
|
||||
$items = array();
|
||||
$parents = isset($_ALL_CLASSES['parents'][$this->class]) ? $_ALL_CLASSES['parents'][$this->class] : null;
|
||||
|
||||
if($parents) {
|
||||
foreach($parents as $parent) {
|
||||
if(!in_array($parent, $_ALL_CLASSES['parents']['TableListField'])) {
|
||||
$items[] = $parent . '_Popup';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$items[] = $this->class . '_Popup';
|
||||
|
||||
return implode(' ', $items);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the db-fieldname of the currently used has_one-relationship.
|
||||
*/
|
||||
|
85
manifest/ClassLoader.php
Normal file
85
manifest/ClassLoader.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* A class that handles loading classes and interfaces from a class manifest
|
||||
* instance.
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage manifest
|
||||
*/
|
||||
class SS_ClassLoader {
|
||||
|
||||
/**
|
||||
* @var SS_ClassLoader
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* @var SS_ClassManifest[]
|
||||
*/
|
||||
protected $manifests = array();
|
||||
|
||||
/**
|
||||
* @return SS_ClassLoader
|
||||
*/
|
||||
public static function instance() {
|
||||
return self::$instance ? self::$instance : self::$instance = new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently active class manifest instance that is used for
|
||||
* loading classes.
|
||||
*
|
||||
* @return SS_ClassManifest
|
||||
*/
|
||||
public function getManifest() {
|
||||
return $this->manifests[count($this->manifests) - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a class manifest instance onto the top of the stack. This will
|
||||
* also include any module configuration files at the same time.
|
||||
*
|
||||
* @param SS_ClassManifest $manifest
|
||||
*/
|
||||
public function pushManifest(SS_ClassManifest $manifest) {
|
||||
$this->manifests[] = $manifest;
|
||||
|
||||
foreach ($manifest->getConfigs() as $config) {
|
||||
require_once $config;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SS_ClassManifest
|
||||
*/
|
||||
public function popManifest() {
|
||||
return array_pop($this->manifests);
|
||||
}
|
||||
|
||||
public function registerAutoloader() {
|
||||
spl_autoload_register(array($this, 'loadClass'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a class or interface if it is present in the currently active
|
||||
* manifest.
|
||||
*
|
||||
* @param string $class
|
||||
*/
|
||||
public function loadClass($class) {
|
||||
if ($path = $this->getManifest()->getItemPath($class)) {
|
||||
require_once $path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a class or interface name exists in the manifest.
|
||||
*
|
||||
* @param string $class
|
||||
* @return bool
|
||||
*/
|
||||
public function classExists($class) {
|
||||
return class_exists($class, false) || $this->getManifest()->getItemPath($class);
|
||||
}
|
||||
|
||||
}
|
361
manifest/ClassManifest.php
Normal file
361
manifest/ClassManifest.php
Normal file
@ -0,0 +1,361 @@
|
||||
<?php
|
||||
/**
|
||||
* A utility class which builds a manifest of all classes, interfaces and some
|
||||
* additional items present in a directory, and caches it.
|
||||
*
|
||||
* It finds the following information:
|
||||
* - Class and interface names and paths.
|
||||
* - All direct and indirect descendants of a class.
|
||||
* - All implementors of an interface.
|
||||
* - All module configuration files.
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage manifest
|
||||
*/
|
||||
class SS_ClassManifest {
|
||||
|
||||
const CONF_FILE = '_config.php';
|
||||
|
||||
protected $base;
|
||||
protected $tests;
|
||||
protected $cache;
|
||||
protected $cacheKey;
|
||||
|
||||
protected $classes = array();
|
||||
protected $roots = array();
|
||||
protected $children = array();
|
||||
protected $descendants = array();
|
||||
protected $interfaces = array();
|
||||
protected $implementors = array();
|
||||
protected $configs = array();
|
||||
|
||||
/**
|
||||
* @return TokenisedRegularExpression
|
||||
*/
|
||||
public static function get_class_parser() {
|
||||
return new TokenisedRegularExpression(array(
|
||||
0 => T_CLASS,
|
||||
1 => T_WHITESPACE,
|
||||
2 => array(T_STRING, 'can_jump_to' => array(7, 14), 'save_to' => 'className'),
|
||||
3 => T_WHITESPACE,
|
||||
4 => T_EXTENDS,
|
||||
5 => T_WHITESPACE,
|
||||
6 => array(T_STRING, 'save_to' => 'extends', 'can_jump_to' => 14),
|
||||
7 => T_WHITESPACE,
|
||||
8 => T_IMPLEMENTS,
|
||||
9 => T_WHITESPACE,
|
||||
10 => array(T_STRING, 'can_jump_to' => 14, 'save_to' => 'interfaces[]'),
|
||||
11 => array(T_WHITESPACE, 'optional' => true),
|
||||
12 => array(',', 'can_jump_to' => 10),
|
||||
13 => array(T_WHITESPACE, 'can_jump_to' => 10),
|
||||
14 => array(T_WHITESPACE, 'optional' => true),
|
||||
15 => '{',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TokenisedRegularExpression
|
||||
*/
|
||||
public static function get_interface_parser() {
|
||||
return new TokenisedRegularExpression(array(
|
||||
0 => T_INTERFACE,
|
||||
1 => T_WHITESPACE,
|
||||
2 => array(T_STRING, 'can_jump_to' => 7, 'save_to' => 'interfaceName'),
|
||||
3 => T_WHITESPACE,
|
||||
4 => T_EXTENDS,
|
||||
5 => T_WHITESPACE,
|
||||
6 => array(T_STRING, 'save_to' => 'extends'),
|
||||
7 => array(T_WHITESPACE, 'optional' => true),
|
||||
8 => '{',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs and initialises a new class manifest, either loading the data
|
||||
* from the cache or re-scanning for classes.
|
||||
*
|
||||
* @param string $base The manifest base path.
|
||||
* @param bool $includeTests Include the contents of "tests" directories.
|
||||
* @param bool $forceRegen Force the manifest to be regenerated.
|
||||
* @param bool $cache If the manifest is regenerated, cache it.
|
||||
*/
|
||||
public function __construct($base, $includeTests = false, $forceRegen = false, $cache = true) {
|
||||
$this->base = $base;
|
||||
$this->tests = $includeTests;
|
||||
|
||||
$this->cache = SS_Cache::factory('SS_ClassManifest', 'Core', array(
|
||||
'automatic_serialization' => true,
|
||||
'lifetime' => null
|
||||
));
|
||||
$this->cacheKey = $this->tests ? 'manifest_tests' : 'manifest';
|
||||
|
||||
if (!$forceRegen && $data = $this->cache->load($this->cacheKey)) {
|
||||
$this->classes = $data['classes'];
|
||||
$this->descendants = $data['descendants'];
|
||||
$this->interfaces = $data['interfaces'];
|
||||
$this->implementors = $data['implementors'];
|
||||
$this->configs = $data['configs'];
|
||||
} else {
|
||||
$this->regenerate($cache);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file path to a class or interface if it exists in the
|
||||
* manifest.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string|null
|
||||
*/
|
||||
public function getItemPath($name) {
|
||||
$name = strtolower($name);
|
||||
|
||||
if (isset($this->classes[$name])) {
|
||||
return $this->classes[$name];
|
||||
} elseif (isset($this->interfaces[$name])) {
|
||||
return $this->interfaces[$name];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of lowercased class names to file paths.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getClasses() {
|
||||
return $this->classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a lowercase array of all the class names in the manifest.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getClassNames() {
|
||||
return array_keys($this->classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all the descendant data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDescendants() {
|
||||
return $this->descendants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing all the descendants (direct and indirect)
|
||||
* of a class.
|
||||
*
|
||||
* @param string|object $class
|
||||
* @return array
|
||||
*/
|
||||
public function getDescendantsOf($class) {
|
||||
if (is_object($class)) {
|
||||
$class = get_class($class);
|
||||
}
|
||||
|
||||
$lClass = strtolower($class);
|
||||
|
||||
if (array_key_exists($lClass, $this->descendants)) {
|
||||
return $this->descendants[$lClass];
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of lowercased interface names to file locations.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getInterfaces() {
|
||||
return $this->interfaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of lowercased interface names to the classes the implement
|
||||
* them.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getImplementors() {
|
||||
return $this->implementors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the class names that implement a certain
|
||||
* interface.
|
||||
*
|
||||
* @param string $interface
|
||||
* @return array
|
||||
*/
|
||||
public function getImplementorsOf($interface) {
|
||||
$interface = strtolower($interface);
|
||||
|
||||
if (array_key_exists($interface, $this->implementors)) {
|
||||
return $this->implementors[$interface];
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of paths to module config files.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getConfigs() {
|
||||
return $this->configs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely regenerates the manifest file.
|
||||
*
|
||||
* @param bool $cache Cache the result.
|
||||
*/
|
||||
public function regenerate($cache = true) {
|
||||
$reset = array(
|
||||
'classes', 'roots', 'children', 'descendants', 'interfaces',
|
||||
'implementors', 'configs'
|
||||
);
|
||||
|
||||
// Reset the manifest so stale info doesn't cause errors.
|
||||
foreach ($reset as $reset) {
|
||||
$this->$reset = array();
|
||||
}
|
||||
|
||||
$finder = new ManifestFileFinder();
|
||||
$finder->setOptions(array(
|
||||
'name_regex' => '/\.php$/',
|
||||
'ignore_files' => array('index.php', 'main.php', 'cli-script.php'),
|
||||
'ignore_tests' => !$this->tests,
|
||||
'file_callback' => array($this, 'handleFile')
|
||||
));
|
||||
$finder->find($this->base);
|
||||
|
||||
foreach ($this->roots as $root) {
|
||||
$this->coalesceDescendants($root);
|
||||
}
|
||||
|
||||
if ($cache) {
|
||||
$data = array(
|
||||
'classes' => $this->classes,
|
||||
'descendants' => $this->descendants,
|
||||
'interfaces' => $this->interfaces,
|
||||
'implementors' => $this->implementors,
|
||||
'configs' => $this->configs
|
||||
);
|
||||
$this->cache->save($data, $this->cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
public function handleFile($basename, $pathname, $depth) {
|
||||
if ($depth == 1 && $basename == self::CONF_FILE) {
|
||||
$this->configs[] = $pathname;
|
||||
return;
|
||||
}
|
||||
|
||||
$classes = null;
|
||||
$interfaces = null;
|
||||
|
||||
// The results of individual file parses are cached, since only a few
|
||||
// files will have changed and TokenisedRegularExpression is quite
|
||||
// slow. A combination of the file name and file contents hash are used,
|
||||
// since just using the datetime lead to problems with upgrading.
|
||||
$file = file_get_contents($pathname);
|
||||
$key = preg_replace('/[^a-zA-Z0-9_]/', '_', $basename) . '_' . md5($file);
|
||||
|
||||
if ($data = $this->cache->load($key)) {
|
||||
$valid = (
|
||||
isset($data['classes']) && isset($data['interfaces'])
|
||||
&& is_array($data['classes']) && is_array($data['interfaces'])
|
||||
);
|
||||
|
||||
if ($valid) {
|
||||
$classes = $data['classes'];
|
||||
$interfaces = $data['interfaces'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$classes) {
|
||||
$tokens = token_get_all($file);
|
||||
$classes = self::get_class_parser()->findAll($tokens);
|
||||
$interfaces = self::get_interface_parser()->findAll($tokens);
|
||||
|
||||
$cache = array('classes' => $classes, 'interfaces' => $interfaces);
|
||||
$this->cache->save($cache, $key, array('fileparse'));
|
||||
}
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$name = $class['className'];
|
||||
$extends = isset($class['extends']) ? $class['extends'] : null;
|
||||
$implements = isset($class['interfaces']) ? $class['interfaces'] : null;
|
||||
|
||||
if (array_key_exists($name, $this->classes)) {
|
||||
throw new Exception(sprintf(
|
||||
'There are two files containing the "%s" class: "%s" and "%s"',
|
||||
$name, $this->classes[$name], $pathname
|
||||
));
|
||||
}
|
||||
|
||||
$this->classes[strtolower($name)] = $pathname;
|
||||
|
||||
if ($extends) {
|
||||
$extends = strtolower($extends);
|
||||
|
||||
if (!isset($this->children[$extends])) {
|
||||
$this->children[$extends] = array($name);
|
||||
} else {
|
||||
$this->children[$extends][] = $name;
|
||||
}
|
||||
} else {
|
||||
$this->roots[] = $name;
|
||||
}
|
||||
|
||||
if ($implements) foreach ($implements as $interface) {
|
||||
$interface = strtolower($interface);
|
||||
|
||||
if (!isset($this->implementors[$interface])) {
|
||||
$this->implementors[$interface] = array($name);
|
||||
} else {
|
||||
$this->implementors[$interface][] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($interfaces as $interface) {
|
||||
$this->interfaces[strtolower($interface['interfaceName'])] = $pathname;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively coalesces direct child information into full descendant
|
||||
* information.
|
||||
*
|
||||
* @param string $class
|
||||
* @return array
|
||||
*/
|
||||
protected function coalesceDescendants($class) {
|
||||
$result = array();
|
||||
$lClass = strtolower($class);
|
||||
|
||||
if (array_key_exists($lClass, $this->children)) {
|
||||
$this->descendants[$lClass] = array();
|
||||
|
||||
foreach ($this->children[$lClass] as $class) {
|
||||
$this->descendants[$lClass] = array_merge(
|
||||
$this->descendants[$lClass],
|
||||
array($class),
|
||||
$this->coalesceDescendants($class)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->descendants[$lClass];
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
65
manifest/ManifestFileFinder.php
Normal file
65
manifest/ManifestFileFinder.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* An extension to the default file finder with some extra filters to faciliate
|
||||
* autoload and template manifest generation:
|
||||
* - Only modules with _config.php files arescanned.
|
||||
* - If a _manifest_exclude file is present inside a directory it is ignored.
|
||||
* - Assets and module language directories are ignored.
|
||||
* - Module tests directories are skipped if the ignore_tests option is not
|
||||
* set to false.
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage manifest
|
||||
*/
|
||||
class ManifestFileFinder extends SS_FileFinder {
|
||||
|
||||
const CONFIG_FILE = '_config.php';
|
||||
const EXCLUDE_FILE = '_manifest_exclude';
|
||||
const LANG_DIR = 'lang';
|
||||
const TESTS_DIR = 'tests';
|
||||
|
||||
public static $default_options = array(
|
||||
'include_themes' => false,
|
||||
'ignore_tests' => true,
|
||||
'min_depth' => 1
|
||||
);
|
||||
|
||||
public function acceptDir($basename, $pathname, $depth) {
|
||||
// Skip over the assets directory in the site root.
|
||||
if ($depth == 1 && $basename == ASSETS_DIR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip over any lang directories in the top level of the module.
|
||||
if ($depth == 2 && $basename == self::LANG_DIR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we're not in testing mode, then skip over the tests directory in
|
||||
// the module root.
|
||||
if ($this->getOption('ignore_tests') && $depth == 2 && $basename == self::TESTS_DIR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore any directories which contain a _manifest_exclude file.
|
||||
if (file_exists($pathname . '/' . self::EXCLUDE_FILE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only include top level module directories which have a configuration
|
||||
// _config.php file. However, if we're in themes mode then include
|
||||
// the themes dir without a config file.
|
||||
$lackingConfig = (
|
||||
$depth == 1
|
||||
&& !($this->getOption('include_themes') && $basename == THEMES_DIR)
|
||||
&& !file_exists($pathname . '/' . self::CONFIG_FILE)
|
||||
);
|
||||
|
||||
if ($lackingConfig) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::acceptDir($basename, $pathname, $depth);
|
||||
}
|
||||
|
||||
}
|
95
manifest/TemplateLoader.php
Normal file
95
manifest/TemplateLoader.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles finding templates from a stack of template manifest objects.
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage manifest
|
||||
*/
|
||||
class SS_TemplateLoader {
|
||||
|
||||
/**
|
||||
* @var SS_TemplateLoader
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* @var SS_TemplateManifest[]
|
||||
*/
|
||||
protected $manifests = array();
|
||||
|
||||
/**
|
||||
* @return SS_TemplateLoader
|
||||
*/
|
||||
public static function instance() {
|
||||
return self::$instance ? self::$instance : self::$instance = new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently active template manifest instance.
|
||||
*
|
||||
* @return SS_TemplateManifest
|
||||
*/
|
||||
public function getManifest() {
|
||||
return $this->manifests[count($this->manifests) - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SS_TemplateManifest $manifest
|
||||
*/
|
||||
public function pushManifest(SS_TemplateManifest $manifest) {
|
||||
$this->manifests[] = $manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SS_TemplateManifest
|
||||
*/
|
||||
public function popManifest() {
|
||||
return array_pop($this->manifests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find possible candidate templates from a set of template
|
||||
* names and a theme.
|
||||
*
|
||||
* The template names can be passed in as plain strings, or be in the
|
||||
* format "type/name", where type is the type of template to search for
|
||||
* (e.g. Includes, Layout).
|
||||
*
|
||||
* @param string|array $templates
|
||||
* @param string $theme
|
||||
* @return array
|
||||
*/
|
||||
public function findTemplates($templates, $theme = null) {
|
||||
$result = array();
|
||||
|
||||
foreach ((array) $templates as $template) {
|
||||
$found = false;
|
||||
|
||||
if (strpos($template, '/')) {
|
||||
list($type, $template) = explode('/', $template, 2);
|
||||
} else {
|
||||
$type = null;
|
||||
}
|
||||
|
||||
if ($candidates = $this->getManifest()->getTemplate($template)) {
|
||||
if ($theme && isset($candidates['themes'][$theme])) {
|
||||
$found = $candidates['themes'][$theme];
|
||||
} else {
|
||||
unset($candidates['themes']);
|
||||
$found = $candidates;
|
||||
}
|
||||
|
||||
if ($found) {
|
||||
if ($type && isset($found[$type])) {
|
||||
$found = array('main' => $found[$type]);
|
||||
}
|
||||
|
||||
$result = array_merge($found, $result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
154
manifest/TemplateManifest.php
Normal file
154
manifest/TemplateManifest.php
Normal file
@ -0,0 +1,154 @@
|
||||
<?php
|
||||
/**
|
||||
* A class which builds a manifest of all templates present in a directory,
|
||||
* in both modules and themes.
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage manifest
|
||||
*/
|
||||
class SS_TemplateManifest {
|
||||
|
||||
const TEMPLATES_DIR = 'templates';
|
||||
|
||||
protected $base;
|
||||
protected $tests;
|
||||
protected $cache;
|
||||
protected $cacheKey;
|
||||
protected $inited;
|
||||
protected $forceRegen;
|
||||
protected $templates = array();
|
||||
|
||||
/**
|
||||
* Constructs a new template manifest. The manifest is not actually built
|
||||
* or loaded from cache until needed.
|
||||
*
|
||||
* @param string $base The base path.
|
||||
* @param bool $includeTests Include tests in the manifest.
|
||||
* @param bool $forceRegen Force the manifest to be regenerated.
|
||||
*/
|
||||
public function __construct($base, $includeTests = false, $forceRegen = false) {
|
||||
$this->base = $base;
|
||||
$this->tests = $includeTests;
|
||||
|
||||
$this->cacheKey = $this->tests ? 'manifest_tests' : 'manifest';
|
||||
$this->forceRegen = $forceRegen;
|
||||
|
||||
$this->cache = SS_Cache::factory('SS_TemplateManifest', 'Core', array(
|
||||
'automatic_serialization' => true,
|
||||
'lifetime' => null
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all template information. The map is in the following
|
||||
* format:
|
||||
*
|
||||
* <code>
|
||||
* array(
|
||||
* 'moduletemplate' => array(
|
||||
* 'main' => '/path/to/module/templates/Main.ss'
|
||||
* ),
|
||||
* 'include' => array(
|
||||
* 'include' => '/path/to/module/templates/Includes/Include.ss'
|
||||
* ),
|
||||
* 'page' => array(
|
||||
* 'themes' => array(
|
||||
* 'blackcandy' => array(
|
||||
* 'main' => '/path/to/theme/Page.ss'
|
||||
* 'Layout' => '/path/to/theme/Layout/Page.ss'
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* </code>
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTemplates() {
|
||||
if (!$this->inited) {
|
||||
$this->init();
|
||||
}
|
||||
|
||||
return $this->templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of possible candidate templates that match a certain
|
||||
* template name.
|
||||
*
|
||||
* This is the same as extracting an individual array element from
|
||||
* {@link SS_TemplateManifest::getTemplates()}.
|
||||
*
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
public function getTemplate($name) {
|
||||
if (!$this->inited) {
|
||||
$this->init();
|
||||
}
|
||||
|
||||
$name = strtolower($name);
|
||||
|
||||
if (array_key_exists($name, $this->templates)) {
|
||||
return $this->templates[$name];
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerates the manifest by scanning the base path.
|
||||
*
|
||||
* @param bool $cache
|
||||
*/
|
||||
public function regenerate($cache = true) {
|
||||
$finder = new ManifestFileFinder();
|
||||
$finder->setOptions(array(
|
||||
'name_regex' => '/\.ss$/',
|
||||
'include_themes' => true,
|
||||
'ignore_tests' => !$this->tests,
|
||||
'file_callback' => array($this, 'handleFile')
|
||||
));
|
||||
$finder->find($this->base);
|
||||
|
||||
if ($cache) {
|
||||
$this->cache->save($this->templates, $this->cacheKey);
|
||||
}
|
||||
|
||||
$this->inited = true;
|
||||
}
|
||||
|
||||
public function handleFile($basename, $pathname, $depth) {
|
||||
if (strpos($pathname, $this->base . '/' . THEMES_DIR) === 0) {
|
||||
$start = strlen($this->base . '/' . THEMES_DIR) + 1;
|
||||
$theme = substr($pathname, $start);
|
||||
$theme = substr($theme, 0, strpos($theme, '/'));
|
||||
$theme = strtok($theme, '_');
|
||||
} else {
|
||||
$theme = null;
|
||||
}
|
||||
|
||||
$type = basename(dirname($pathname));
|
||||
$name = strtolower(substr($basename, 0, -3));
|
||||
|
||||
if ($type == self::TEMPLATES_DIR) {
|
||||
$type = 'main';
|
||||
}
|
||||
|
||||
if ($theme) {
|
||||
$this->templates[$name]['themes'][$theme][$type] = $pathname;
|
||||
} else {
|
||||
$this->templates[$name][$type] = $pathname;
|
||||
}
|
||||
}
|
||||
|
||||
protected function init() {
|
||||
if (!$this->forceRegen && $data = $this->cache->load($this->cacheKey)) {
|
||||
$this->templates = $data;
|
||||
$this->inited = true;
|
||||
} else {
|
||||
$this->regenerate();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
* @package sapphire
|
||||
* @subpackage core
|
||||
*/
|
||||
class TokenisedRegularExpression extends Object {
|
||||
class TokenisedRegularExpression {
|
||||
/**
|
||||
* The regular expression definition
|
||||
*/
|
||||
@ -14,7 +14,6 @@ class TokenisedRegularExpression extends Object {
|
||||
|
||||
function __construct($expression) {
|
||||
$this->expression = $expression;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
function findAll($tokens) {
|
@ -4,12 +4,18 @@
|
||||
* @subpackage tests
|
||||
*/
|
||||
class ClassInfoTest extends SapphireTest {
|
||||
|
||||
|
||||
public function testExists() {
|
||||
$this->assertTrue(ClassInfo::exists('Object'));
|
||||
$this->assertTrue(ClassInfo::exists('ClassInfoTest'));
|
||||
$this->assertTrue(ClassInfo::exists('stdClass'));
|
||||
}
|
||||
|
||||
function testSubclassesFor() {
|
||||
$this->assertEquals(
|
||||
ClassInfo::subclassesFor('ClassInfoTest_BaseClass'),
|
||||
array(
|
||||
0 => 'ClassInfoTest_BaseClass',
|
||||
'ClassInfoTest_BaseClass' => 'ClassInfoTest_BaseClass',
|
||||
'ClassInfoTest_ChildClass' => 'ClassInfoTest_ChildClass',
|
||||
'ClassInfoTest_GrandChildClass' => 'ClassInfoTest_GrandChildClass'
|
||||
),
|
||||
@ -33,10 +39,62 @@ class ClassInfoTest extends SapphireTest {
|
||||
// 'ClassInfo::classes_for_folder() returns additional classes not matching the filename'
|
||||
// );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @covers ClassInfo::baseDataClass()
|
||||
*/
|
||||
public function testBaseDataClass() {
|
||||
$this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('ClassInfoTest_BaseClass'));
|
||||
$this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('ClassInfoTest_ChildClass'));
|
||||
$this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('ClassInfoTest_GrandChildClass'));
|
||||
|
||||
$this->setExpectedException('Exception');
|
||||
ClassInfo::baseDataClass('DataObject');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ClassInfo::ancestry()
|
||||
*/
|
||||
public function testAncestry() {
|
||||
$ancestry = ClassInfo::ancestry('SiteTree');
|
||||
$expect = ArrayLib::valuekey(array(
|
||||
'Object',
|
||||
'ViewableData',
|
||||
'DataObject',
|
||||
'SiteTree'
|
||||
));
|
||||
$this->assertEquals($expect, $ancestry);
|
||||
|
||||
$ancestry = ClassInfo::ancestry('SiteTree', true);
|
||||
$this->assertEquals(array('SiteTree' => 'SiteTree'), $ancestry);
|
||||
|
||||
$this->setExpectedException('Exception');
|
||||
ClassInfo::ancestry(42);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ClassInfo::dataClassesFor()
|
||||
*/
|
||||
public function testDataClassesFor() {
|
||||
$expect = array(
|
||||
'ClassInfoTest_BaseDataClass' => 'ClassInfoTest_BaseDataClass',
|
||||
'ClassInfoTest_HasFields' => 'ClassInfoTest_HasFields'
|
||||
);
|
||||
|
||||
$classes = array(
|
||||
'ClassInfoTest_BaseDataClass',
|
||||
'ClassInfoTest_NoFields',
|
||||
'ClassInfoTest_HasFields'
|
||||
);
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$this->assertEquals($expect, ClassInfo::dataClassesFor($class));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ClassInfoTest_BaseClass {
|
||||
class ClassInfoTest_BaseClass extends DataObject {
|
||||
|
||||
}
|
||||
|
||||
@ -47,4 +105,11 @@ class ClassInfoTest_ChildClass extends ClassInfoTest_BaseClass {
|
||||
class ClassInfoTest_GrandChildClass extends ClassInfoTest_ChildClass {
|
||||
|
||||
}
|
||||
?>
|
||||
|
||||
class ClassInfoTest_BaseDataClass extends DataObject {
|
||||
public static $db = array('Title' => 'Varchar');
|
||||
}
|
||||
class ClassInfoTest_NoFields extends ClassInfoTest_BaseDataClass {}
|
||||
class ClassInfoTest_HasFields extends ClassInfoTest_NoFields {
|
||||
public static $db = array('Description' => 'Varchar');
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ class FullTestSuite {
|
||||
* @return Array
|
||||
*/
|
||||
public static function get_all_tests() {
|
||||
ManifestBuilder::load_test_manifest();
|
||||
TestRunner::use_test_manifest();
|
||||
$tests = ClassInfo::subclassesFor('SapphireTest');
|
||||
array_shift($tests);
|
||||
|
||||
|
@ -1,111 +0,0 @@
|
||||
<?php
|
||||
|
||||
$filesystemFixture = array(
|
||||
'rahbeast/',
|
||||
'rahbeast/_config.php' => <<<PHP
|
||||
<?php
|
||||
|
||||
global \$project;
|
||||
\$project = 'rahbeast';
|
||||
|
||||
PHP
|
||||
,
|
||||
'sapphire/',
|
||||
'sapphire/_config.php',
|
||||
'sapphire/MyClass.php' => <<<PHP
|
||||
<?php
|
||||
|
||||
interface MyInterface {
|
||||
|
||||
}
|
||||
interface OtherInterface {
|
||||
|
||||
}
|
||||
class BaseClass {
|
||||
|
||||
}
|
||||
|
||||
/* This is a comment before */
|
||||
class MyClass extends Object {
|
||||
|
||||
}
|
||||
|
||||
/* This is a comment between */
|
||||
|
||||
class MyClass_Other extends DataObject implements MyInterface {
|
||||
|
||||
}
|
||||
|
||||
class MyClass_Final extends DataObject implements MyInterface, OtherInterface {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Here we have a class in a comment:
|
||||
class MyClass_InComment extends DataObject {
|
||||
|
||||
}
|
||||
|
||||
This class definition isn't named correctly:
|
||||
class UnrelatedCLass extends DataObject {
|
||||
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
// This is another class definition: class MyClass_InSlashSlashComment extends DataObject {
|
||||
|
||||
// Here's a class definition in a string:
|
||||
\$string = <<<MYCODE
|
||||
|
||||
class MyClass_InHeredocString extends DataObject {
|
||||
|
||||
}
|
||||
|
||||
MYCODE;
|
||||
|
||||
// Other definitions inside strings:
|
||||
\$string2 = 'class MyClass_InSingleQuoteString extends DataObject {
|
||||
}'
|
||||
\$string3 = "class MyClass_InDoubleQuoteString extends DataObject {
|
||||
}
|
||||
|
||||
I've included \"an escaped quote\" in this to ensure that it can handle that.
|
||||
"
|
||||
|
||||
/* let's define a class between two strings to confirm that it gets discovered */
|
||||
class MyClass_ClassBetweenTwoStrings extends DataObject {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
\$string4 = "class MyClass_InDoubleQuoteString extends DataObject {
|
||||
}
|
||||
|
||||
I've included \"an escaped quote\" in this to ensure that it can handle that.
|
||||
"
|
||||
|
||||
?>
|
||||
PHP
|
||||
,
|
||||
'sapphire/subdir/',
|
||||
'sapphire/subdir/SubDirClass.php' => <<<PHP
|
||||
<?php
|
||||
|
||||
class SubDirClass extends BaseClass implements MyInterace, OtherInterface {
|
||||
|
||||
}
|
||||
PHP
|
||||
,
|
||||
'sapphire/subdir/SubDirClass.php',
|
||||
'otherdir/',
|
||||
'otherdir/OtherFile.php' => <<<PHP
|
||||
<?php
|
||||
|
||||
class OtherFile extends BaseClass {
|
||||
|
||||
}
|
||||
PHP
|
||||
,
|
||||
);
|
@ -1,176 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package sapphire
|
||||
* @subpackage tests
|
||||
*/
|
||||
class ManifestBuilderTest extends SapphireTest {
|
||||
function testManifest() {
|
||||
$baseFolder = TEMP_FOLDER . '/manifest-test-asdfasdfasdf';
|
||||
$manifestInfo = ManifestBuilder::get_manifest_info($baseFolder);
|
||||
global $project;
|
||||
|
||||
$this->assertEquals("$baseFolder/sapphire/MyClass.php", $manifestInfo['globals']['_CLASS_MANIFEST']['myclass']);
|
||||
$this->assertEquals("$baseFolder/sapphire/subdir/SubDirClass.php", $manifestInfo['globals']['_CLASS_MANIFEST']['subdirclass']);
|
||||
$this->assertNotContains('OtherFile', array_keys($manifestInfo['globals']['_CLASS_MANIFEST']));
|
||||
|
||||
$this->assertContains('MyClass', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists']));
|
||||
$this->assertContains('MyClass_Other', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists']));
|
||||
$this->assertContains('MyClass_Final', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists']));
|
||||
$this->assertContains('MyClass_ClassBetweenTwoStrings', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists']));
|
||||
|
||||
// Check aspects of PHP file
|
||||
$manifest = ManifestBuilder::generate_php_file($manifestInfo);
|
||||
// Debug::message($manifest);
|
||||
$this->assertEquals(1, preg_match('/^<\?php/', $manifest), "Starts with <?php");
|
||||
$this->assertEquals(1, preg_match('/\$_CLASS_MANIFEST\s*=\s*array/m', $manifest), "\$_CLASS_MANIFEST exists");
|
||||
$this->assertEquals(1, preg_match('/\$_TEMPLATE_MANIFEST\s*=\s*array/m', $manifest), "\$_TEMPLATE_MANIFEST exists");
|
||||
$this->assertEquals(1, preg_match('/\$_CSS_MANIFEST\s*=\s*array/m', $manifest), "\$_CSS_MANIFEST exists");
|
||||
$this->assertEquals(1, preg_match('/\$_ALL_CLASSES\s*=\s*array/m', $manifest), "\$_ALL_CLASSES exists");
|
||||
|
||||
$this->assertEquals(1, preg_match('/require_once\("[^"]+rahbeast\/_config.php"\);/i', $manifest), "rahbeast/_config.php included");
|
||||
$this->assertEquals(1, preg_match('/require_once\("[^"]+sapphire\/_config.php"\);/i', $manifest), "sapphire/_config.php included");
|
||||
}
|
||||
|
||||
function testManifestIgnoresClassesInComments() {
|
||||
$baseFolder = TEMP_FOLDER . '/manifest-test-asdfasdfasdf';
|
||||
global $project;
|
||||
|
||||
$manifestInfo = ManifestBuilder::get_manifest_info($baseFolder);
|
||||
|
||||
/* Our fixture defines the class MyClass_InComment inside a comment, so it shouldn't be included in the class manifest. */
|
||||
$this->assertNotContains('myclass_incomment', array_keys($manifestInfo['globals']['_CLASS_MANIFEST']));
|
||||
$this->assertNotContains('MyClass_InComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists']));
|
||||
$this->assertNotContains('MyClass_InComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['parents']));
|
||||
|
||||
/* Our fixture defines the class MyClass_InSlashSlashComment inside a //-style comment, so it shouldn't be included in the class manifest. */
|
||||
$this->assertNotContains('myclass_inslashslashcomment', array_keys($manifestInfo['globals']['_CLASS_MANIFEST']));
|
||||
$this->assertNotContains('MyClass_InSlashSlashComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists']));
|
||||
$this->assertNotContains('MyClass_InSlashSlashComment', array_keys($manifestInfo['globals']['_ALL_CLASSES']['parents']));
|
||||
}
|
||||
|
||||
function testManifestIgnoresClassesInStrings() {
|
||||
$baseFolder = TEMP_FOLDER . '/manifest-test-asdfasdfasdf';
|
||||
$manifestInfo = ManifestBuilder::get_manifest_info($baseFolder);
|
||||
|
||||
/* If a class defintion is listed in a single quote string, then it shouldn't be inlcuded. Here we have put a class definition for MyClass_InSingleQuoteString inside a single-quoted string */
|
||||
$this->assertNotContains('myclass_insinglequotestring', array_keys($manifestInfo['globals']['_CLASS_MANIFEST']));
|
||||
$this->assertNotContains('MyClass_InSingleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists']));
|
||||
$this->assertNotContains('MyClass_InSingleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['parents']));
|
||||
|
||||
/* Ditto for double quotes. Here we have put a class definition for MyClass_InDoubleQuoteString inside a double-quoted string. */
|
||||
$this->assertNotContains('myclass_indoublequotestring', array_keys($manifestInfo['globals']['_CLASS_MANIFEST']));
|
||||
$this->assertNotContains('MyClass_InDoubleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists']));
|
||||
$this->assertNotContains('MyClass_InDoubleQuoteString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['parents']));
|
||||
|
||||
/* Finally, we need to ensure that class definitions inside heredoc strings aren't included. Here, we have defined the class MyClass_InHeredocString inside a heredoc string. */
|
||||
$this->assertNotContains('myclass_inheredocstring', array_keys($manifestInfo['globals']['_CLASS_MANIFEST']));
|
||||
$this->assertNotContains('MyClass_InHeredocString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists']));
|
||||
$this->assertNotContains('MyClass_InHeredocString', array_keys($manifestInfo['globals']['_ALL_CLASSES']['parents']));
|
||||
}
|
||||
|
||||
|
||||
function testClassNamesDontHaveToBeTheSameAsFileNames() {
|
||||
$baseFolder = TEMP_FOLDER . '/manifest-test-asdfasdfasdf';
|
||||
$manifestInfo = ManifestBuilder::get_manifest_info($baseFolder);
|
||||
|
||||
$this->assertContains('BaseClass', array_keys($manifestInfo['globals']['_ALL_CLASSES']['exists']));
|
||||
}
|
||||
|
||||
protected $originalClassManifest, $originalProject, $originalAllClasses;
|
||||
protected static $test_fixture_project;
|
||||
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Trick the auto-loder into loading this class before we muck with the manifest
|
||||
new TokenisedRegularExpression(null);
|
||||
|
||||
include('tests/ManifestBuilderTest.fixture.inc');
|
||||
|
||||
// Build the fixture specified above
|
||||
$baseFolder = TEMP_FOLDER . '/manifest-test-asdfasdfasdf/';
|
||||
|
||||
if(file_exists($baseFolder)) Filesystem::removeFolder($baseFolder);
|
||||
mkdir($baseFolder);
|
||||
|
||||
foreach($filesystemFixture as $i => $item) {
|
||||
if(is_numeric($i)) {
|
||||
$itemContent = null;
|
||||
} else {
|
||||
$itemContent = $item;
|
||||
$item = $i;
|
||||
}
|
||||
|
||||
// Directory
|
||||
if(substr($item,-1) == '/') {
|
||||
mkdir($baseFolder . $item);
|
||||
} else {
|
||||
touch($baseFolder . $item);
|
||||
if($itemContent) {
|
||||
$fh = fopen($baseFolder . $item, 'wb');
|
||||
fwrite($fh, $itemContent);
|
||||
fclose($fh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global $_CLASS_MANIFEST, $_ALL_CLASSES, $project;
|
||||
|
||||
$this->originalAllClasses = $_ALL_CLASSES;
|
||||
$this->originalClassManifest = $_CLASS_MANIFEST;
|
||||
$this->originalProject = $project;
|
||||
|
||||
// Because it's difficult to run multiple tests on a piece of code that uses require_once, we keep a copy of the
|
||||
// $project value.
|
||||
if(self::$test_fixture_project) $project = self::$test_fixture_project;
|
||||
|
||||
global $project;
|
||||
}
|
||||
|
||||
function testThemeRetrieval() {
|
||||
$ds = DIRECTORY_SEPARATOR;
|
||||
$testThemeBaseDir = TEMP_FOLDER . $ds . 'test-themes';
|
||||
|
||||
if(file_exists($testThemeBaseDir)) Filesystem::removeFolder($testThemeBaseDir);
|
||||
|
||||
mkdir($testThemeBaseDir);
|
||||
mkdir($testThemeBaseDir . $ds . 'blackcandy');
|
||||
mkdir($testThemeBaseDir . $ds . 'blackcandy_blog');
|
||||
mkdir($testThemeBaseDir . $ds . 'darkshades');
|
||||
mkdir($testThemeBaseDir . $ds . 'darkshades_blog');
|
||||
|
||||
$this->assertEquals(array(
|
||||
'blackcandy' => 'blackcandy',
|
||||
'darkshades' => 'darkshades'
|
||||
), ManifestBuilder::get_themes($testThemeBaseDir), 'Our test theme directory contains 2 themes');
|
||||
|
||||
$this->assertEquals(array(
|
||||
'blackcandy' => 'blackcandy',
|
||||
'blackcandy_blog' => 'blackcandy_blog',
|
||||
'darkshades' => 'darkshades',
|
||||
'darkshades_blog' => 'darkshades_blog'
|
||||
), ManifestBuilder::get_themes($testThemeBaseDir, true), 'Our test theme directory contains 2 themes and 2 sub-themes');
|
||||
|
||||
// Remove all the test themes we created
|
||||
Filesystem::removeFolder($testThemeBaseDir);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
global $_CLASS_MANIFEST, $_ALL_CLASSES, $project;
|
||||
|
||||
if(!self::$test_fixture_project) self::$test_fixture_project = $project;
|
||||
|
||||
$project = $this->originalProject;
|
||||
$_CLASS_MANIFEST = $this->originalClassManifest;
|
||||
$_ALL_CLASSES = $this->originalAllClasses;
|
||||
|
||||
// Kill the folder after we're done
|
||||
$baseFolder = TEMP_FOLDER . '/manifest-test-asdfasdfasdf/';
|
||||
Filesystem::removeFolder($baseFolder);
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
@ -499,6 +499,38 @@ after')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers SSViewer::get_themes()
|
||||
*/
|
||||
function testThemeRetrieval() {
|
||||
$ds = DIRECTORY_SEPARATOR;
|
||||
$testThemeBaseDir = TEMP_FOLDER . $ds . 'test-themes';
|
||||
|
||||
if(file_exists($testThemeBaseDir)) Filesystem::removeFolder($testThemeBaseDir);
|
||||
|
||||
mkdir($testThemeBaseDir);
|
||||
mkdir($testThemeBaseDir . $ds . 'blackcandy');
|
||||
mkdir($testThemeBaseDir . $ds . 'blackcandy_blog');
|
||||
mkdir($testThemeBaseDir . $ds . 'darkshades');
|
||||
mkdir($testThemeBaseDir . $ds . 'darkshades_blog');
|
||||
|
||||
$this->assertEquals(array(
|
||||
'blackcandy' => 'blackcandy',
|
||||
'darkshades' => 'darkshades'
|
||||
), SSViewer::get_themes($testThemeBaseDir), 'Our test theme directory contains 2 themes');
|
||||
|
||||
$this->assertEquals(array(
|
||||
'blackcandy' => 'blackcandy',
|
||||
'blackcandy_blog' => 'blackcandy_blog',
|
||||
'darkshades' => 'darkshades',
|
||||
'darkshades_blog' => 'darkshades_blog'
|
||||
), SSViewer::get_themes($testThemeBaseDir, true), 'Our test theme directory contains 2 themes and 2 sub-themes');
|
||||
|
||||
// Remove all the test themes we created
|
||||
Filesystem::removeFolder($testThemeBaseDir);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,7 +49,7 @@ PHP
|
||||
}
|
||||
|
||||
function testClassDefParser() {
|
||||
$parser = ManifestBuilder::getClassDefParser();
|
||||
$parser = SS_ClassManifest::get_class_parser();
|
||||
|
||||
$tokens = $this->getTokens();
|
||||
|
||||
@ -79,7 +79,7 @@ PHP
|
||||
}
|
||||
|
||||
function testInterfaceDefParser() {
|
||||
$parser = ManifestBuilder::getInterfaceDefParser();
|
||||
$parser = SS_ClassManifest::get_interface_parser();
|
||||
|
||||
$tokens = $this->getTokens();
|
||||
|
||||
|
@ -94,13 +94,13 @@ class RestfulServiceTest extends SapphireTest {
|
||||
*/
|
||||
function testIncorrectData() {
|
||||
$connection = new RestfulService(Director::absoluteBaseURL(), 0);
|
||||
$test1 = $connection->request('RestfulServiceTest_Controller/invalid?usetestmanifest=1&flush=1');
|
||||
$test1 = $connection->request('RestfulServiceTest_Controller/invalid');
|
||||
$test1->xpath("\\fail");
|
||||
}
|
||||
|
||||
function testHttpErrorWithoutCache() {
|
||||
$connection = new RestfulServiceTest_MockRestfulService(Director::absoluteBaseURL(), 0);
|
||||
$response = $connection->request('RestfulServiceTest_Controller/httpErrorWithoutCache?usetestmanifest=1&flush=1');
|
||||
$response = $connection->request('RestfulServiceTest_Controller/httpErrorWithoutCache');
|
||||
|
||||
$this->assertEquals(400, $response->getStatusCode());
|
||||
$this->assertFalse($response->getCachedBody());
|
||||
@ -110,7 +110,7 @@ class RestfulServiceTest extends SapphireTest {
|
||||
|
||||
function testHttpErrorWithCache() {
|
||||
$subUrl = 'RestfulServiceTest_Controller/httpErrorWithCache?usetestmanifest=1&flush=1';
|
||||
$connection = new RestfulService(Director::absoluteBaseURL(), 0);
|
||||
$connection = new RestfulServiceTest_MockErrorService(Director::absoluteBaseURL(), 0);
|
||||
$this->createFakeCachedResponse($connection, $subUrl);
|
||||
$response = $connection->request($subUrl);
|
||||
|
||||
@ -271,4 +271,15 @@ class RestfulServiceTest_MockRestfulService extends RestfulService {
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A mock service that returns a 400 error for requests.
|
||||
*/
|
||||
class RestfulServiceTest_MockErrorService extends RestfulService {
|
||||
|
||||
public function curlRequest() {
|
||||
return new RestfulService_Response('<error>HTTP Error</error>', 400);
|
||||
}
|
||||
|
||||
}
|
||||
|
2
tests/bootstrap.php
Executable file → Normal file
2
tests/bootstrap.php
Executable file → Normal file
@ -32,4 +32,4 @@ if(!class_exists('Object')) {
|
||||
|
||||
$_SERVER['REQUEST_URI'] = BASE_URL . '/dev';
|
||||
|
||||
ManifestBuilder::load_test_manifest();
|
||||
TestRunner::use_test_manifest();
|
114
tests/filesystem/FileFinderTest.php
Normal file
114
tests/filesystem/FileFinderTest.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the {@link SS_FileFinder} class.
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage tests
|
||||
*/
|
||||
class FileFinderTest extends SapphireTest {
|
||||
|
||||
protected $base;
|
||||
|
||||
public function __construct() {
|
||||
$this->base = dirname(__FILE__) . '/fixtures/filefinder';
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function testBasicOperation() {
|
||||
$this->assertFinderFinds(new SS_FileFinder(), array(
|
||||
'file1.txt',
|
||||
'file2.txt',
|
||||
'dir1/dir1file1.txt',
|
||||
'dir1/dir1file2.txt',
|
||||
'dir1/dir2/dir2file1.txt',
|
||||
'dir1/dir2/dir3/dir3file1.txt'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidOptionThrowsException() {
|
||||
$finder = new SS_FileFinder();
|
||||
$finder->setOption('this_doesnt_exist', 'ok');
|
||||
}
|
||||
|
||||
public function testFilenameRegex() {
|
||||
$finder = new SS_FileFinder();
|
||||
$finder->setOption('name_regex', '/file2\.txt$/');
|
||||
|
||||
$this->assertFinderFinds(
|
||||
$finder,
|
||||
array(
|
||||
'file2.txt',
|
||||
'dir1/dir1file2.txt'),
|
||||
'The finder only returns files matching the name regex.');
|
||||
}
|
||||
|
||||
public function testIgnoreFiles() {
|
||||
$finder = new SS_FileFinder();
|
||||
$finder->setOption('ignore_files', array('file1.txt', 'dir1file1.txt', 'dir2file1.txt'));
|
||||
|
||||
$this->assertFinderFinds(
|
||||
$finder,
|
||||
array(
|
||||
'file2.txt',
|
||||
'dir1/dir1file2.txt',
|
||||
'dir1/dir2/dir3/dir3file1.txt'),
|
||||
'The finder ignores files with the basename in the ignore_files setting.');
|
||||
}
|
||||
|
||||
public function testIgnoreDirs() {
|
||||
$finder = new SS_FileFinder();
|
||||
$finder->setOption('ignore_dirs', array('dir2'));
|
||||
|
||||
$this->assertFinderFinds(
|
||||
$finder,
|
||||
array(
|
||||
'file1.txt',
|
||||
'file2.txt',
|
||||
'dir1/dir1file1.txt',
|
||||
'dir1/dir1file2.txt'),
|
||||
'The finder ignores directories in ignore_dirs.');
|
||||
}
|
||||
|
||||
public function testMinDepth() {
|
||||
$finder = new SS_FileFinder();
|
||||
$finder->setOption('min_depth', 2);
|
||||
|
||||
$this->assertFinderFinds(
|
||||
$finder,
|
||||
array(
|
||||
'dir1/dir2/dir2file1.txt',
|
||||
'dir1/dir2/dir3/dir3file1.txt'),
|
||||
'The finder respects the min depth setting.');
|
||||
}
|
||||
|
||||
public function testMaxDepth() {
|
||||
$finder = new SS_FileFinder();
|
||||
$finder->setOption('max_depth', 1);
|
||||
|
||||
$this->assertFinderFinds(
|
||||
$finder,
|
||||
array(
|
||||
'file1.txt',
|
||||
'file2.txt',
|
||||
'dir1/dir1file1.txt',
|
||||
'dir1/dir1file2.txt'),
|
||||
'The finder respects the max depth setting.');
|
||||
}
|
||||
|
||||
public function assertFinderFinds($finder, $expect, $message = null) {
|
||||
$found = $finder->find($this->base);
|
||||
|
||||
foreach ($expect as $k => $file) {
|
||||
$expect[$k] = "{$this->base}/$file";
|
||||
}
|
||||
|
||||
sort($expect);
|
||||
sort($found);
|
||||
|
||||
$this->assertEquals($expect, $found, $message);
|
||||
}
|
||||
|
||||
}
|
0
tests/filesystem/fixtures/filefinder/file1.txt
Normal file
0
tests/filesystem/fixtures/filefinder/file1.txt
Normal file
0
tests/filesystem/fixtures/filefinder/file2.txt
Normal file
0
tests/filesystem/fixtures/filefinder/file2.txt
Normal file
@ -1,3 +1 @@
|
||||
<?php
|
||||
Object::extend('i18nTestModule', 'i18nTestModuleDecorator');
|
||||
?>
|
@ -29,41 +29,17 @@ class i18nTest extends SapphireTest {
|
||||
$this->alternateBasePath = Director::baseFolder() . "/sapphire/tests/i18n/_fakewebroot";
|
||||
$this->alternateBaseSavePath = TEMP_FOLDER . '/i18nTextCollectorTest_webroot';
|
||||
FileSystem::makeFolder($this->alternateBaseSavePath);
|
||||
|
||||
// SSViewer and ManifestBuilder don't support different webroots, hence we set the paths manually
|
||||
global $_CLASS_MANIFEST;
|
||||
$_CLASS_MANIFEST['i18nTestModule'] = $this->alternateBasePath . '/i18ntestmodule/code/i18nTestModule.php';
|
||||
$_CLASS_MANIFEST['i18nTestModule_Addition'] = $this->alternateBasePath . '/i18ntestmodule/code/i18nTestModule.php';
|
||||
$_CLASS_MANIFEST['i18nTestModuleDecorator'] = $this->alternateBasePath . '/i18nothermodule/code/i18nTestModuleDecorator.php';
|
||||
|
||||
global $_ALL_CLASSES;
|
||||
$_ALL_CLASSES['parents']['i18nTestModule'] = array('DataObject'=>'DataObject','Object'=>'Object');
|
||||
$_ALL_CLASSES['parents']['i18nTestModule_Addition'] = array('Object'=>'Object');
|
||||
$_ALL_CLASSES['parents']['i18nTestModuleDecorator'] = array('DataObjectDecorator'=>'DataObjectDecorator','Object'=>'Object');
|
||||
|
||||
global $_TEMPLATE_MANIFEST;
|
||||
$_TEMPLATE_MANIFEST['i18nTestModule.ss'] = array(
|
||||
'main' => $this->alternateBasePath . '/i18ntestmodule/templates/i18nTestModule.ss',
|
||||
'Layout' => $this->alternateBasePath . '/i18ntestmodule/templates/Layout/i18nTestModule.ss',
|
||||
);
|
||||
$_TEMPLATE_MANIFEST['i18nTestModuleInclude.ss'] = array(
|
||||
'Includes' => $this->alternateBasePath . '/i18ntestmodule/templates/Includes/i18nTestModuleInclude.ss',
|
||||
);
|
||||
|
||||
// Push a template loader running from the fake webroot onto the stack.
|
||||
$manifest = new SS_TemplateManifest($this->alternateBasePath, false, true);
|
||||
$manifest->regenerate(false);
|
||||
SS_TemplateLoader::instance()->pushManifest($manifest);
|
||||
|
||||
$this->originalLocale = i18n::get_locale();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
//FileSystem::removeFolder($this->tmpBasePath);
|
||||
|
||||
global $_CLASS_MANIFEST;
|
||||
unset($_CLASS_MANIFEST['i18nTestModule']);
|
||||
unset($_CLASS_MANIFEST['i18nTestModule_Addition']);
|
||||
|
||||
global $_TEMPLATE_MANIFEST;
|
||||
unset($_TEMPLATE_MANIFEST['i18nTestModule.ss']);
|
||||
unset($_TEMPLATE_MANIFEST['i18nTestModuleInclude.ss']);
|
||||
|
||||
SS_TemplateLoader::instance()->popManifest();
|
||||
i18n::set_locale($this->originalLocale);
|
||||
|
||||
parent::tearDown();
|
||||
|
@ -18,53 +18,28 @@ class i18nTextCollectorTest extends SapphireTest {
|
||||
*/
|
||||
protected $alternateBasePath;
|
||||
|
||||
protected $manifest;
|
||||
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->alternateBasePath = Director::baseFolder() . "/sapphire/tests/i18n/_fakewebroot";
|
||||
$this->alternateBaseSavePath = TEMP_FOLDER . '/i18nTextCollectorTest_webroot';
|
||||
FileSystem::makeFolder($this->alternateBaseSavePath);
|
||||
|
||||
// SSViewer and ManifestBuilder don't support different webroots, hence we set the paths manually
|
||||
global $_CLASS_MANIFEST;
|
||||
$_CLASS_MANIFEST['i18nTestModule'] = $this->alternateBasePath . '/i18ntestmodule/code/i18nTestModule.php';
|
||||
$_CLASS_MANIFEST['i18nTestModule_Addition'] = $this->alternateBasePath . '/i18ntestmodule/code/i18nTestModule.php';
|
||||
$_CLASS_MANIFEST['i18nTestModuleDecorator'] = $this->alternateBasePath . '/i18nothermodule/code/i18nTestModuleDecorator.php';
|
||||
|
||||
global $_ALL_CLASSES;
|
||||
$_ALL_CLASSES['parents']['i18nTestModule'] = array('DataObject'=>'DataObject','Object'=>'Object');
|
||||
$_ALL_CLASSES['parents']['i18nTestModule_Addition'] = array('Object'=>'Object');
|
||||
$_ALL_CLASSES['parents']['i18nTestModuleDecorator'] = array('DataObjectDecorator'=>'DataObjectDecorator','Object'=>'Object');
|
||||
|
||||
global $_TEMPLATE_MANIFEST;
|
||||
$_TEMPLATE_MANIFEST['i18nTestModule.ss'] = array(
|
||||
'main' => $this->alternateBasePath . '/i18ntestmodule/templates/i18nTestModule.ss',
|
||||
'Layout' => $this->alternateBasePath . '/i18ntestmodule/templates/Layout/i18nTestModule.ss',
|
||||
);
|
||||
$_TEMPLATE_MANIFEST['i18nTestModuleInclude.ss'] = array(
|
||||
'Includes' => $this->alternateBasePath . '/i18ntestmodule/templates/Includes/i18nTestModuleInclude.ss',
|
||||
// Push a class and template loader running from the fake webroot onto
|
||||
// the stack.
|
||||
$this->manifest = new SS_ClassManifest(
|
||||
$this->alternateBasePath, false, true, false
|
||||
);
|
||||
|
||||
$_TEMPLATE_MANIFEST['i18nTestTheme1.ss'] = array(
|
||||
'main' => $this->alternateBasePath . '/themes/testtheme1/templates/i18nTestTheme1.ss',
|
||||
'Layout' => $this->alternateBasePath . '/themes/testtheme1/templates/Layout/i18nTestTheme1.ss',
|
||||
);
|
||||
$_TEMPLATE_MANIFEST['i18nTestTheme1Include.ss'] = array(
|
||||
'Includes' => $this->alternateBasePath . '/themes/testtheme1/templates/Includes/i18nTestTheme1Include.ss',
|
||||
);
|
||||
$manifest = new SS_TemplateManifest($this->alternateBasePath, false, true);
|
||||
$manifest->regenerate(false);
|
||||
SS_TemplateLoader::instance()->pushManifest($manifest);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
//FileSystem::removeFolder($this->tmpBasePath);
|
||||
|
||||
global $_CLASS_MANIFEST;
|
||||
unset($_CLASS_MANIFEST['i18nTestModule']);
|
||||
unset($_CLASS_MANIFEST['i18nTestModule_Addition']);
|
||||
|
||||
global $_TEMPLATE_MANIFEST;
|
||||
unset($_TEMPLATE_MANIFEST['i18nTestModule.ss']);
|
||||
unset($_TEMPLATE_MANIFEST['i18nTestModuleInclude.ss']);
|
||||
|
||||
SS_TemplateLoader::instance()->popManifest();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
@ -413,6 +388,9 @@ PHP;
|
||||
function testCollectFromThemesTemplates() {
|
||||
$c = new i18nTextCollector();
|
||||
|
||||
$theme = SSViewer::current_theme();
|
||||
SSViewer::set_theme('testtheme1');
|
||||
|
||||
$templateFilePath = $this->alternateBasePath . '/themes/testtheme1/templates/Layout/i18nTestTheme1.ss';
|
||||
$html = file_get_contents($templateFilePath);
|
||||
$matches = $c->collectFromTemplate($html, 'themes/testtheme1', 'i18nTestTheme1.ss');
|
||||
@ -462,6 +440,8 @@ PHP;
|
||||
$matches['i18nTestTheme1Include.ss.SPRINTFINCLUDENONAMESPACE'],
|
||||
array('Theme1 My include replacement no namespace: %s', null, null)
|
||||
);
|
||||
|
||||
SSViewer::set_theme($theme);
|
||||
}
|
||||
|
||||
function testCollectFromFilesystemAndWriteMasterTables() {
|
||||
|
114
tests/manifest/ClassManifestTest.php
Normal file
114
tests/manifest/ClassManifestTest.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the {@link SS_ClassManifest} class.
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage tests
|
||||
*/
|
||||
class ClassManifestTest extends SapphireTest {
|
||||
|
||||
protected $base;
|
||||
protected $manifest;
|
||||
protected $manifestTests;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->base = dirname(__FILE__) . '/fixtures/classmanifest';
|
||||
$this->manifest = new SS_ClassManifest($this->base, false, true, false);
|
||||
$this->manifestTests = new SS_ClassManifest($this->base, true, true, false);
|
||||
}
|
||||
|
||||
public function testGetItemPath() {
|
||||
$expect = array(
|
||||
'CLASSA' => 'module/classes/ClassA.php',
|
||||
'ClassA' => 'module/classes/ClassA.php',
|
||||
'classa' => 'module/classes/ClassA.php',
|
||||
'INTERFACEA' => 'module/interfaces/InterfaceA.php',
|
||||
'InterfaceA' => 'module/interfaces/InterfaceA.php',
|
||||
'interfacea' => 'module/interfaces/InterfaceA.php'
|
||||
);
|
||||
|
||||
foreach ($expect as $name => $path) {
|
||||
$this->assertEquals("{$this->base}/$path", $this->manifest->getItemPath($name));
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetClasses() {
|
||||
$expect = array(
|
||||
'classa' => "{$this->base}/module/classes/ClassA.php",
|
||||
'classb' => "{$this->base}/module/classes/ClassB.php",
|
||||
'classc' => "{$this->base}/module/classes/ClassC.php",
|
||||
'classd' => "{$this->base}/module/classes/ClassD.php"
|
||||
);
|
||||
$this->assertEquals($expect, $this->manifest->getClasses());
|
||||
}
|
||||
|
||||
public function testGetClassNames() {
|
||||
$this->assertEquals(
|
||||
array('classa', 'classb', 'classc', 'classd'),
|
||||
$this->manifest->getClassNames());
|
||||
}
|
||||
|
||||
public function testGetDescendants() {
|
||||
$expect = array(
|
||||
'classa' => array('ClassC', 'ClassD'),
|
||||
'classc' => array('ClassD')
|
||||
);
|
||||
$this->assertEquals($expect, $this->manifest->getDescendants());
|
||||
}
|
||||
|
||||
public function testGetDescendantsOf() {
|
||||
$expect = array(
|
||||
'CLASSA' => array('ClassC', 'ClassD'),
|
||||
'classa' => array('ClassC', 'ClassD'),
|
||||
'CLASSC' => array('ClassD'),
|
||||
'classc' => array('ClassD')
|
||||
);
|
||||
|
||||
foreach ($expect as $class => $desc) {
|
||||
$this->assertEquals($desc, $this->manifest->getDescendantsOf($class));
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetInterfaces() {
|
||||
$expect = array(
|
||||
'interfacea' => "{$this->base}/module/interfaces/InterfaceA.php",
|
||||
'interfaceb' => "{$this->base}/module/interfaces/InterfaceB.php"
|
||||
);
|
||||
$this->assertEquals($expect, $this->manifest->getInterfaces());
|
||||
}
|
||||
|
||||
public function testGetImplementors() {
|
||||
$expect = array(
|
||||
'interfacea' => array('ClassB'),
|
||||
'interfaceb' => array('ClassC')
|
||||
);
|
||||
$this->assertEquals($expect, $this->manifest->getImplementors());
|
||||
}
|
||||
|
||||
public function testGetImplementorsOf() {
|
||||
$expect = array(
|
||||
'INTERFACEA' => array('ClassB'),
|
||||
'interfacea' => array('ClassB'),
|
||||
'INTERFACEB' => array('ClassC'),
|
||||
'interfaceb' => array('ClassC')
|
||||
);
|
||||
|
||||
foreach ($expect as $interface => $impl) {
|
||||
$this->assertEquals($impl, $this->manifest->getImplementorsOf($interface));
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetConfigs() {
|
||||
$expect = array("{$this->base}/module/_config.php");
|
||||
$this->assertEquals($expect, $this->manifest->getConfigs());
|
||||
$this->assertEquals($expect, $this->manifestTests->getConfigs());
|
||||
}
|
||||
|
||||
public function testTestManifestIncludesTestClasses() {
|
||||
$this->assertNotContains('testclassa', array_keys($this->manifest->getClasses()));
|
||||
$this->assertContains('testclassa', array_keys($this->manifestTests->getClasses()));
|
||||
}
|
||||
|
||||
}
|
61
tests/manifest/ManifestFileFinderTest.php
Normal file
61
tests/manifest/ManifestFileFinderTest.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the {@link ManifestFileFinder} class.
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage tests
|
||||
*/
|
||||
class ManifestFileFinderTest extends SapphireTest {
|
||||
|
||||
protected $base;
|
||||
|
||||
public function __construct() {
|
||||
$this->base = dirname(__FILE__) . '/fixtures/manifestfilefinder';
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function assertFinderFinds($finder, $expect, $message = null) {
|
||||
$found = $finder->find($this->base);
|
||||
|
||||
foreach ($expect as $k => $file) {
|
||||
$expect[$k] = "{$this->base}/$file";
|
||||
}
|
||||
|
||||
sort($expect);
|
||||
sort($found);
|
||||
|
||||
$this->assertEquals($expect, $found, $message);
|
||||
}
|
||||
|
||||
public function testBasicOperation() {
|
||||
$finder = new ManifestFileFinder();
|
||||
$finder->setOption('name_regex', '/\.txt$/');
|
||||
|
||||
$this->assertFinderFinds($finder, array(
|
||||
'module/module.txt'
|
||||
));
|
||||
}
|
||||
|
||||
public function testIgnoreTests() {
|
||||
$finder = new ManifestFileFinder();
|
||||
$finder->setOption('name_regex', '/\.txt$/');
|
||||
$finder->setOption('ignore_tests', false);
|
||||
|
||||
$this->assertFinderFinds($finder, array(
|
||||
'module/module.txt',
|
||||
'module/tests/tests.txt'
|
||||
));
|
||||
}
|
||||
|
||||
public function testIncludeThemes() {
|
||||
$finder = new ManifestFileFinder();
|
||||
$finder->setOption('name_regex', '/\.txt$/');
|
||||
$finder->setOption('include_themes', true);
|
||||
|
||||
$this->assertFinderFinds($finder, array(
|
||||
'module/module.txt',
|
||||
'themes/themes.txt'
|
||||
));
|
||||
}
|
||||
|
||||
}
|
46
tests/manifest/TemplateLoaderTest.php
Normal file
46
tests/manifest/TemplateLoaderTest.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the {@link SS_TemplateLoader} class.
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage tests
|
||||
*/
|
||||
class TemplateLoaderTest extends SapphireTest {
|
||||
|
||||
public function testFindTemplates() {
|
||||
$base = dirname(__FILE__) . '/fixtures/templatemanifest';
|
||||
$manifest = new SS_TemplateManifest($base, false, true);
|
||||
$loader = new SS_TemplateLoader();
|
||||
|
||||
$manifest->regenerate(false);
|
||||
$loader->pushManifest($manifest);
|
||||
|
||||
$expectPage = array(
|
||||
'main' => "$base/module/templates/Page.ss",
|
||||
'Layout' => "$base/module/templates/Layout/Page.ss"
|
||||
);
|
||||
$expectPageThemed = array(
|
||||
'main' => "$base/themes/theme/templates/Page.ss",
|
||||
'Layout' => "$base/themes/theme/templates/Layout/Page.ss"
|
||||
);
|
||||
|
||||
$this->assertEquals($expectPage, $loader->findTemplates('Page'));
|
||||
$this->assertEquals($expectPage, $loader->findTemplates(array('Foo', 'Page')));
|
||||
$this->assertEquals($expectPage, $loader->findTemplates('PAGE'));
|
||||
$this->assertEquals($expectPageThemed, $loader->findTemplates('Page', 'theme'));
|
||||
|
||||
$expectPageLayout = array('main' => "$base/module/templates/Layout/Page.ss");
|
||||
$expectPageLayoutThemed = array('main' => "$base/themes/theme/templates/Layout/Page.ss");
|
||||
|
||||
$this->assertEquals($expectPageLayout, $loader->findTemplates('Layout/Page'));
|
||||
$this->assertEquals($expectPageLayout, $loader->findTemplates('Layout/PAGE'));
|
||||
$this->assertEquals($expectPageLayoutThemed, $loader->findTemplates('Layout/Page', 'theme'));
|
||||
|
||||
$expectCustomPage = array(
|
||||
'main' => "$base/module/templates/Page.ss",
|
||||
'Layout' => "$base/module/templates/Layout/CustomPage.ss"
|
||||
);
|
||||
$this->assertEquals($expectCustomPage, $loader->findTemplates(array('CustomPage', 'Page')));
|
||||
}
|
||||
|
||||
}
|
98
tests/manifest/TemplateManifestTest.php
Normal file
98
tests/manifest/TemplateManifestTest.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the template manifest.
|
||||
*
|
||||
* @package sapphire
|
||||
* @subpackage tests
|
||||
*/
|
||||
class TemplateManifestTest extends SapphireTest {
|
||||
|
||||
protected $base;
|
||||
protected $manifest;
|
||||
protected $manifestTests;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->base = dirname(__FILE__) . '/fixtures/templatemanifest';
|
||||
$this->manifest = new SS_TemplateManifest($this->base);
|
||||
$this->manifestTests = new SS_TemplateManifest($this->base, true);
|
||||
|
||||
$this->manifest->regenerate(false);
|
||||
$this->manifestTests->regenerate(false);
|
||||
}
|
||||
|
||||
public function testGetTemplates() {
|
||||
$expect = array(
|
||||
'root' => array(
|
||||
'module' => "{$this->base}/module/Root.ss"
|
||||
),
|
||||
'page' => array(
|
||||
'main' => "{$this->base}/module/templates/Page.ss",
|
||||
'Layout' => "{$this->base}/module/templates/Layout/Page.ss",
|
||||
'themes' => array('theme' => array(
|
||||
'main' => "{$this->base}/themes/theme/templates/Page.ss",
|
||||
'Layout' => "{$this->base}/themes/theme/templates/Layout/Page.ss"
|
||||
))
|
||||
),
|
||||
'custompage' => array(
|
||||
'Layout' => "{$this->base}/module/templates/Layout/CustomPage.ss"
|
||||
),
|
||||
'subfolder' => array(
|
||||
'main' => "{$this->base}/module/subfolder/templates/Subfolder.ss"
|
||||
),
|
||||
'include' => array('themes' => array(
|
||||
'theme' => array(
|
||||
'Includes' => "{$this->base}/themes/theme/templates/Includes/Include.ss"
|
||||
)
|
||||
))
|
||||
);
|
||||
|
||||
$expectTests = $expect;
|
||||
$expectTests['test'] = array(
|
||||
'main' => "{$this->base}/module/tests/templates/Test.ss"
|
||||
);
|
||||
|
||||
$manifest = $this->manifest->getTemplates();
|
||||
$manifestTests = $this->manifestTests->getTemplates();
|
||||
|
||||
ksort($expect);
|
||||
ksort($expectTests);
|
||||
ksort($manifest);
|
||||
ksort($manifestTests);
|
||||
|
||||
$this->assertEquals(
|
||||
$expect, $manifest,
|
||||
'All templates are correctly loaded in the manifest.'
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
$expectTests, $manifestTests,
|
||||
'The test manifest is the same, but includes test templates.'
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetTemplate() {
|
||||
$expectPage = array(
|
||||
'main' => "{$this->base}/module/templates/Page.ss",
|
||||
'Layout' => "{$this->base}/module/templates/Layout/Page.ss",
|
||||
'themes' => array('theme' => array(
|
||||
'main' => "{$this->base}/themes/theme/templates/Page.ss",
|
||||
'Layout' => "{$this->base}/themes/theme/templates/Layout/Page.ss"
|
||||
))
|
||||
);
|
||||
|
||||
$expectTests = array(
|
||||
'main' => "{$this->base}/module/tests/templates/Test.ss"
|
||||
);
|
||||
|
||||
$this->assertEquals($expectPage, $this->manifest->getTemplate('Page'));
|
||||
$this->assertEquals($expectPage, $this->manifest->getTemplate('PAGE'));
|
||||
$this->assertEquals($expectPage, $this->manifestTests->getTemplate('Page'));
|
||||
$this->assertEquals($expectPage, $this->manifestTests->getTemplate('PAGE'));
|
||||
|
||||
$this->assertEquals(array(), $this->manifest->getTemplate('Test'));
|
||||
$this->assertEquals($expectTests, $this->manifestTests->getTemplate('Test'));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
class ClassA { }
|
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
class ClassB implements InterfaceA { }
|
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
class ClassC extends ClassA implements InterfaceB { }
|
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
class ClassD extends ClassC { }
|
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
interface InterfaceA { }
|
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
interface InterfaceB { }
|
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
class TestClassA { }
|
@ -0,0 +1 @@
|
||||
<?php
|
@ -0,0 +1 @@
|
||||
<?php
|
0
tests/manifest/fixtures/manifestfilefinder/root.txt
Normal file
0
tests/manifest/fixtures/manifestfilefinder/root.txt
Normal file
Loading…
Reference in New Issue
Block a user