Source for file BitSystem.php
Documentation is available at BitSystem.php
* Main bitweaver systems functions
* Copyright (c) 2004 bitweaver.org
* All Rights Reserved. See below for details and a complete list of authors.
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See http://www.gnu.org/copyleft/lesser.html for details
* Virtual base class (as much as one can have such things in PHP) for all
* derived tikiwiki classes that require database access.
* @author spider <spider@steelsun.com>
require_once( KERNEL_PKG_PATH . 'BitSingleton.php' );
require_once( KERNEL_PKG_PATH . 'BitDate.php' );
require_once( THEMES_PKG_PATH . 'BitSmarty.php' );
require_once( KERNEL_PKG_PATH . 'HttpStatusCodes.php' );
define( 'DEFAULT_PACKAGE', 'kernel' );
define( 'CENTER_COLUMN', 'c' );
define( 'HOMEPAGE_LAYOUT', 'home' );
* This is the main system class that does the work of seeing bitweaver has an
* operable environment and has methods for modifying that environment.
* Currently gBitSystem derives from this class for backward compatibility sake.
* Ultimate goal is to put system code from BitBase here, and base code from
* gBitSystem (code that ALL features need) into BitBase and code gBitSystem that
* is Package specific should be moved into that package
* @author spider <spider@steelsun.com>
// Initiate class variables
// Essential information about packages
// Cross Reference Package Directory Name => Package Key used as index into $mPackages
// Contains site style information
// Information about package menus used in all menu modules and top bar
// The currently active page
// Modules that need to be inserted during installation
// Javascript to be added to the <body onload> attribute
// Javascript to be added to the <body onunload> attribute
// Used by packages to register notification events that can be subscribed to.
// Used to store contents of kernel_config
// Used to monitor if ::registerPackage() was called. This is used to determine whether to auto-register a package
// The name of the package that is currently being processed
// Debug HTML to be displayed just after the HTML headers
protected static $singleton = null;
return static::$singleton;
// === BitSystem constructor
* base constructor, auto assigns member db variable
// Constructor receiving a PEAR::Db database object.
// Call DB constructor which will create the database member variable
$this->mTimer = $gBitTimer;
$this->mServerTimestamp = new BitDate();
// Critical Preflight Checks
// Set the separator for PHP generated tags to be & instead of &
// This is necessary for XHTML compliance
ini_set( "arg_separator.output", "&" );
// Remove automatic quotes added to POST/COOKIE by PHP
foreach( $_REQUEST as $k => $v ) {
if( $ret = parent::loadFromCache( $pCacheKey ) ) {
$ret->mAppMenu = array();
* Load all preferences and store them in $this->mConfig
* @param $pPackage optionally get preferences only for selected package
$whereClause = ' WHERE `package`=? ';
$query = "SELECT `config_name` ,`config_value`, `package` FROM `" . BIT_DB_PREFIX . "kernel_config` " . $whereClause;
if( $rs = $this->mDb->query( $query, $queryVars, - 1, - 1 ) ) {
while( $row = $rs->fetchRow() ) {
$this->mConfig[$row['config_name']] = $row['config_value'];
* Add getConfig / setConfig for more uniform handling of config variables instead of spreading global vars.
* easily get the value of any given preference stored in kernel_config
function getConfig( $pName, $pDefault = NULL ) {
return( empty( $this->mConfig[$pName] ) ? $pDefault : $this->mConfig[$pName] );
* retreive a group of config variables
foreach( $matching_keys as $key=> $value ) {
if ( empty( $pSelectValue ) || ( !empty( $pSelectValue ) && $this->mConfig[$value] == $pSelectValue )) {
$new_array[$value] = $this->mConfig[$value];
* storeConfigMatch set a group of config variables
* @param string $pPattern Perl regular expression
* @param string $pSelectValue only manipulate settings with this value set
* @param string $pNewValue New value that should be set for the matching settings (NULL will remove the entries from the DB)
* @param string $pPackage Package for which the settings are
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
function storeConfigMatch( $pPattern, $pSelectValue = "", $pNewValue = NULL, $pPackage = NULL ) {
foreach( $matchingKeys as $key => $config_name ) {
if( empty( $pSelectValue ) || ( !empty( $pSelectValue ) && $this->mConfig[$config_name] == $pSelectValue )) {
$this->storeConfig( $config_name, $pNewValue, $pPackage );
* Set a hash value in the mConfig hash. This does *NOT* store the value in
* the database. It does no checking for existing or duplicate values. the
* main point of this function is to limit direct accessing of the mConfig
* hash. I will probably make mConfig private one day.
* @param string Hash key for the mConfig value
* @param string Value for the mConfig hash key
* bitweaver needs lots of settings just to operate.
* loadConfig assigns itself the default preferences, then loads just the differences from the database.
* In storeConfig (and only when storeConfig is called) we make a second copy of defaults to see if
* preferences you are changing is different from the default.
* if it is the same, don't store it!
* So instead updating the whole prefs table, only updat "delta" of the changes delta from defaults.
function storeConfig( $pName, $pValue, $pPackage = NULL ) {
//stop undefined offset error being thrown after packages are installed
// store the pref if we have a value _AND_ it is different from the default
if( ( empty( $this->mConfig[$pName] ) || ( $this->mConfig[$pName] != $pValue ))) {
// make sure the value doesn't exceede database limitations
$pValue = substr( $pValue, 0, 250 );
// store the preference in multisites, if used
if( $this->isPackageActive( 'multisites' ) && @BitBase::verifyId( $gMultisites->mMultisiteId ) && isset ( $gMultisites->mConfig[$pName] )) {
$query = "UPDATE `". BIT_DB_PREFIX. "multisite_preferences` SET `config_value`=? WHERE `multisite_id`=? AND `config_name`=?";
$result = $this->mDb->query( $query, array( empty( $pValue ) ? '' : $pValue, $gMultisites->mMultisiteId, $pName ) );
$this->mDb->StartTrans();
$query = "DELETE FROM `". BIT_DB_PREFIX. "kernel_config` WHERE `config_name`=?";
$result = $this->mDb->query( $query, array( $pName ) );
// make sure only non-empty values get saved, including '0'
if( isset ( $pValue ) && ( !empty( $pValue ) || is_numeric( $pValue ))) {
$query = "INSERT INTO `". BIT_DB_PREFIX. "kernel_config`(`config_name`,`config_value`,`package`) VALUES (?,?,?)";
$result = $this->mDb->query( $query, array( $pName, $pValue, strtolower( $pPackage )));
$this->mDb->CompleteTrans();
// Force the ADODB cache to flush
$isCaching = $this->mDb->isCachingActive();
$this->mDb->setCaching( FALSE );
$this->mDb->setCaching( $isCaching );
// <<< expungePackageConfig
* Delete all prefences for the given package
if( !empty( $pPackageName ) ) {
$query = "DELETE FROM `". BIT_DB_PREFIX. "kernel_config` WHERE `package`=?";
$result = $this->mDb->query( $query, array( strtolower( $pPackageName ) ) );
// let's force a reload of the prefs
// === hasValidSenderEmail
* Determines if this site has a legitimate sender address set.
* @param $mid the name of the template for the page content
if( empty( $pSenderEmail ) ) {
$pSenderEmail = $this->getConfig( 'site_sender_email' );
return( !empty( $pSenderEmail ) && !preg_match( '/.*localhost$/', $pSenderEmail ) );
* Smartly determines where error emails should go
} elseif( $this->getConfig( 'site_sender_email' ) ) {
$ret = $this->getConfig( 'site_sender_email' );
} elseif( !empty( $_SERVER['SERVER_ADMIN'] ) ) {
$ret = $_SERVER['SERVER_ADMIN'];
* centralized function for send emails
* @param $mid the name of the template for the page content
$extraHeaders = "Bcc: ". $this->getConfig( 'bcc_email' ). "\r\n";
if( !empty( $pMailHash['Reply-to'] ) ) {
$extraHeaders = "Reply-to: ". $pMailHash['Reply-to']. "\r\n";
$fromEmail = !empty( $pMailHash['from'] ) ? $pMailHash['from'] : $this->getConfig( 'site_sender_email' );
mail($pMailHash['email'],
$pMailHash['subject']. ' '. $_SERVER["SERVER_NAME"],
"From: ". $fromEmail. "\r\nContent-type: text/plain;charset=utf-8\r\n$extraHeaders"
* Set the http status, most notably for 404 not found for deleted content
* @param $pHttpStatus numerical HTTP status, most typically 404 (not found) or 403 (forbidden)
// see if we have a custom status other than 200 OK
* Display the main page template
* @param $mid the name of the template for the page content
* @param $browserTitle a string to be displayed in the top browser bar
* @param $format the output format - xml, ajax, content, full - relays to setRenderFormat
function display( $pMid, $pBrowserTitle = NULL, $pOptionsHash = array() ) {
global $gBitSmarty, $gBitThemes, $gContent;
$gBitSmarty->verifyCompileDir();
// error_log( "HTTP/1.0 ".HttpStatusCodes::getMessageForCode( $this->mHttpStatus )." http://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'] );
// set the correct headers if it hasn't been done yet
if( empty( $gBitThemes->mFormatHeader )) {
// display is the last thing we call and therefore we need to set a default
$gBitThemes->setFormatHeader( !empty( $pOptionsHash['format'] ) ? $pOptionsHash['format'] : 'html' );
// set the desired display mode - this lets bitweaver know what type of page we are viewing
if( empty( $gBitThemes->mDisplayMode )) {
// display is the last thing we call and therefore we need to set a default
$gBitThemes->setDisplayMode( !empty( $pOptionsHash['display_mode'] ) ? $pOptionsHash['display_mode'] : 'display' );
if( $pMid == 'error.tpl' ) {
$pMid = 'bitpackage:kernel/error.tpl';
// only using the default html header will print modules and all the rest of it.
if( $gBitThemes->mFormatHeader != 'html' ) {
$gBitSmarty->display( $pMid );
if( !empty( $pBrowserTitle )) {
// populate meta description with something useful so you are not penalized/ignored by web crawlers
if( is_object( $gContent ) && $gContent->isValid() ) {
if( $summary = $gContent->getField( 'summary' ) ) {
$desc = $gContent->parseData( $summary );
} elseif( $desc = $gContent->getField( 'parsed' ) ) {
} elseif( $summary = $gContent->getField( 'data' ) ) {
$desc = $gContent->parseData( $summary );
$desc = preg_replace( '/\s+/', ' ', $desc); // $gContent->getContentTypeName().': '.
$gBitSmarty->assign( 'metaDescription', substr( strip_tags( $desc ), 0, 256 ) );
$this->preDisplay( $pMid );
$gBitSmarty->assign( 'mid', $pMid );
$gBitSmarty->assign( 'role_model', TRUE );
// Make sure that the gBitSystem symbol available to templates is correct and up-to-date.
print $gBitSmarty->fetch( 'bitpackage:kernel/html.tpl' );
$this->postDisplay( $pMid );
* Take care of any processing that needs to happen just before the template is displayed
function preDisplay( $pMid ) {
global $gCenterPieces, $gBitSmarty, $gBitThemes, $gDefaultCenter;
if( !defined( 'JSCALENDAR_PKG_URL' ) ) {
$gBitThemes->loadLayout();
// check to see if we are working with a dynamic center area
if( $pMid == 'bitpackage:kernel/dynamic.tpl' ) {
$gBitSmarty->assign_by_ref( 'gCenterPieces', $gCenterPieces );
$gBitThemes->preLoadStyle();
/* @TODO - fetch module php files before rendering tpls.
* The basic problem here is center_list and module files are
* processed during page rendering, which means code in those
* files can not set <head> information before rendering. Kinda sucks.
* So what this does is, this calls on a service function allowing any
* package to check if its center or other module file is going to be
* called and gives it a chance to set any information for <head> first.
* Remove when TODO is complete. -wjames5
if( isset ( $gBitUser )) {
//SMARTY3 $gBitUser->invokeServices( 'module_display_function' );
// SMARTY3 require_once( THEMES_PKG_PATH.'modules_inc.php' );
$gBitThemes->loadStyle();
/* force the session to close *before* displaying. Why? Note this very important comment from http://us4.php.net/exec
If you are using sessions and want to start a background process, you might
have the following problem:
The first time you call your script everything goes fine, but when you call it again
and the process is STILL running, your script seems to "freeze" until you kill the
process you started the first time.
You'll have to call session_write_close(); to solve this problem. It has something
to do with the fact that only one script/process can operate at once on a session.
(others will be lockedout until the script finishes)
I don't know why it somehow seems to be influenced by background processes,
but I tried everything and this is the only solution. (i had a perl script that
"daemonized" it's self like the example in the perl manuals)
Took me a long time to figure out, thanks ian@virtisp.net! :-)
... and a similar issue can happen for very long display times.
* Take care of any processing that needs to happen just after the template is displayed
function postDisplay( $pMid ) {
* Set the smarty variables needed to display the help link for a page.
* @param $package Package Name
* @param $context Context of the help within the package
* @param $desc Description of the help link (not the help itself)
function setHelpInfo( $package, $context, $desc ) {
$gBitSmarty->assign( 'TikiHelpInfo', array( 'URL' => 'http://doc.bitweaver.org/wiki/index.php?page=' . $package . $context , 'Desc' => $desc ) );
* find out a packages installation status
* @param $pPackageName the name of the package to test
* where the package name is in the form used to index $mPackages
* 'i' is installed but not active
* 'y' is installed and active
// A package is installed if
// $this->getConfig('package_'.$name) == 'i'
// or $this->getConfig('package_'.$name) == 'y'
// A package is installed and active if
// <package name>_PKG_NAME is defined
// and $this->getConfig('package_'.$name) == 'y'
if( $name == 'kernel' ) {
// we have migrated the old tikiwiki feature_<pac
$ret = $this->getConfig( 'package_'. $name, 'n' );
* check's if a package is active.
* @param $pPackageName the name of the package to test
* where the package name is in the form used to index $mPackages
* See comments in scanPackages for more information
// === isPackageActiveEarly
* check if a package is active; but only do this after making sure a package
* has had it's bit_setup_inc loaded if possible. This func exists for use in
* other packages bit_setup_inc's to avoid dependency on load order and ugly code
* @param $pPackageName the name of the package to test
* where the package name is in the form used to index $mPackages
* See comments in scanPackages for more information
} elseif( $pkgname_l == 'kernel' ) {
// === isPackageInstalled
* check's if a package is Installed
* @param $pPackageName the name of the package to test
* where the package name is in the form used to index $mPackages
* See comments in scanPackages for more information
return( ( $pkgstatus == 'y' ) || ( $pkgstatus == 'i' ) );
* It will verify that the given package is active or it will display the error template and die()
* @param $pPackageName the name of the package to test
* where the package name is in the form used to index $mPackages
* See comments in scanPackages for more information
$this->fatalError( tra("This package is disabled"). ": package_$pPackageName" );
* It will get information about a permissions
* @param $pPermission value of a given permission
if( !empty( $pPermission ) ) {
$sql .= ' WHERE `perm_name`=? ';
} elseif( !empty( $pPackageName ) ) {
$sql .= ' WHERE `package` = ? ';
$ret = $this->mDb->getAssoc( $sql, $bindVars );
* DEPRECATED - this function has been moved into BitPermUser, use that
return $gBitUser->verifyPermission( $pPermission, $pMsg );
* Interupt code execution and show a permission denied message.
* This does not show a big nasty denied message if user is simply not logged in.
* This *could* lead to a user seeing a denied message twice, however this is
* unlikely as logic permission checks should prevent access to non-permed page REQUEST in the first place
* @param $pPermission value of a given permission
* @param $pMsg optional additional information to present to user
global $gBitUser, $gBitSmarty, $gBitThemes;
if( !$gBitUser->isRegistered() ) {
$gBitSmarty->assign( 'template', 'bitpackage:users/login_inc.tpl' );
$gBitSmarty->assign( 'fatalTitle', tra( "Permission denied." ) );
// bit_error_log( "PERMISSION DENIED: $pPermission $pMsg" );
$gBitSmarty->assign( 'msg', tra( $pMsg ) );
$ret = "You do not have the required permissions";
if( !empty( $permDesc[$pPermission]['perm_desc'] ) ) {
if( preg_match( '/administrator,/i', $permDesc[$pPermission]['perm_desc'] ) ) {
$ret .= preg_replace( '/^administrator, can/i', ' to ', $permDesc[$pPermission]['perm_desc'] );
$ret .= preg_replace( '/^can /i', ' to ', $permDesc[$pPermission]['perm_desc'] );
* This code was duplicated _EVERYWHERE_ so here is an easy template to cut that down.
* @param $pFormHash documentation needed
* @param $pMsg documentation needed
$pageTitle = self::getParameter( $pMsg, 'label', 'Please Confirm' );
if( empty( $pParamHash['cancel_url'] ) ) {
$gBitSmarty->assign( 'backJavascript', 'onclick="history.back();"' );
if( !empty( $pFormHash['input'] ) ) {
$gBitSmarty->assign( 'inputFields', $pFormHash['input'] );
unset ( $pFormHash['input'] );
$gBitSmarty->assign( 'msgFields', $pMsg );
$gBitSmarty->assign_by_ref( 'hiddenFields', $pFormHash );
$this->display( 'bitpackage:kernel/confirm.tpl', $pageTitle, array( 'display_mode' => 'edit' ));
* check's if the specfied feature is active
$featureValue = $this->getConfig($pFeatureName);
$ret = !empty( $featureValue ) && ( $featureValue != 'n' );
* It will verify that the given feature is active or it will display the error template and die()
* @param $pFeatureName the name of the package to test
$this->fatalError( tra("This feature is disabled"). ": $pFeatureName" );
* Define name, location and url DEFINE's
if( !isset ( $pRegisterHash['package_name'] )) {
$name = $pRegisterHash['package_name'];
if( !isset ( $pRegisterHash['package_path'] )) {
$path = $pRegisterHash['package_path'];
$this->mPackages[$pkgNameKey]['homeable'] = !empty( $pRegisterHash['homeable'] );
$this->mPackages[$pkgNameKey]['required'] = !empty( $pRegisterHash['required_package'] );
$this->mPackages[$pkgNameKey]['service'] = !empty( $pRegisterHash['service'] ) ? $pRegisterHash['service'] : FALSE;
# n (or empty/null) = Not Active and Not Installed
// set package installed and active flag
if( $this->mPackages[$pkgNameKey]['status'] == 'a' || $this->mPackages[$pkgNameKey]['status'] == 'y' ) {
$this->mPackages[$pkgNameKey]['active_switch'] = TRUE;
$this->mPackages[$pkgNameKey]['active_switch'] = FALSE;
// set package installed flag (can be installed but not active)
if( $this->mPackages[$pkgNameKey]['active_switch'] || $this->mPackages[$pkgNameKey]['status'] == 'i' ) {
$this->mPackages[$pkgNameKey]['installed'] = TRUE;
$this->mPackages[$pkgNameKey]['installed'] = FALSE;
// Define <PACKAGE>_PKG_PATH
$pkgDefine = $pkgName. '_PKG_PATH';
// Define <PACKAGE>_PKG_URL
$pkgDefine = $pkgName. '_PKG_URL';
// Force full URI's for offline or exported content (newsletters, etc.)
// Define <PACKAGE>_PKG_URI
$pkgDefine = $pkgName. '_PKG_URI';
// Define <PACKAGE>_PKG_NAME
$pkgDefine = $pkgName. '_PKG_NAME';
$this->mPackages[$pkgNameKey]['activatable'] = isset ( $pRegisterHash['activatable'] ) ? $pRegisterHash['activatable'] : TRUE;
$this->mPackages[$pkgNameKey]['name'] = $name;
// Define <PACKAGE>_PKG_DIR
$pkgDefine = $pkgName. '_PKG_DIR';
define( $pkgDefine, $package_dir_name );
$this->mPackages[$pkgNameKey]['dir'] = $package_dir_name;
// Define <PACKAGE>_PKG_TITLE
$pkgDefine = $pkgName. '_PKG_TITLE';
$this->mPackages[$pkgNameKey]['dir'] = $package_dir_name;
// Work around for old versions of IIS that do not support $_SERVER['SCRIPT_FILENAME'] - wolff_borg
//remove double-backslashes and return
$_SERVER['SCRIPT_FILENAME'] = str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED'] );
// Define the package we are currently in
// I tried strpos instead of preg_match here, but it didn't like strings that begin with slash?! - spiderr
if( !defined( 'ACTIVE_PACKAGE' ) && ( $scriptDir == constant( $pkgName. '_PKG_DIR' ) || isset ( $_SERVER['ACTIVE_PACKAGE'] ) || preg_match( '!/'. $this->mPackages[$pkgNameKey]['dir']. '/!', $_SERVER['SCRIPT_NAME'] ) || preg_match( '!/'. $pkgNameKey. '/!', $_SERVER['SCRIPT_NAME'] ))) {
if( isset ( $_SERVER['ACTIVE_PACKAGE'] )) {
// perhaps the webserver told us the active package (probably because of mod_rewrites)
$pkgNameKey = $_SERVER['ACTIVE_PACKAGE'];
define( 'ACTIVE_PACKAGE', $pkgNameKey );
* Register global system menu. Due to the startup nature of this method, it need to belong in BitSystem instead of BitThemes, where it would more naturally fit.
function registerAppMenu( $pMenuHash, $pMenuTitle = NULL, $pTitleUrl = NULL, $pMenuTemplate = NULL, $pAdminPanel = FALSE ) {
$menuType = (!empty( $pMenuHash['menu_type'] ) ? $pMenuHash['menu_type'] : 'bar');
$pkg = $pMenuHash['package_name'];
$pMenuHash['style'] = 'display:'. ( ( isset ( $_COOKIE[$pkg. 'menu'] ) && ( $_COOKIE[$pkg. 'menu'] == 'o' ) ) ? 'block;' : 'none;' );
$pMenuHash['is_disabled'] = ( $this->getConfig( 'menu_'. $pkg ) == 'n' );
$pMenuHash['menu_title'] = $this->getConfig( $pkg. '_menu_text',
( !empty( $pMenuHash['menu_title'] )
? $pMenuHash['menu_title']
$pMenuHash['menu_position'] = $this->getConfig( $pkg. '_menu_position',
( !empty( $pMenuHash['menu_position'] )
? $pMenuHash['menu_position']
$this->mAppMenu[$menuType][$pkg] = $pMenuHash;
deprecated( 'Please use a menu registration hash instead of individual parameters: $gBitSystem->registerAppMenu( $menuHash )' );
'menu_title' => $pMenuTitle,
'is_disabled' => ( $this->getConfig( 'menu_'. $pMenuHash ) == 'n' ),
'index_url' => $pTitleUrl,
'menu_template' => $pMenuTemplate,
'admin_panel' => $pAdminPanel,
'style' => 'display:'. ( empty( $pMenuTitle ) || ( isset ( $_COOKIE[$pMenuHash. 'menu'] ) && ( $_COOKIE[$pMenuHash. 'menu'] == 'o' ) ) ? 'block;' : 'none;' )
* @param array $pEventHash
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
* If an unrecoverable error has occurred, this method should be invoked. script exist occurs
* @param string $ pMsg error message to be displayed
* @param string template file used to display error
* @param string error dialog title. default gets site_error_title config, passing '' will result in no title
* @return none this function will DIE DIE DIE!!!
function fatalError( $pMsg, $pTemplate= NULL, $pErrorTitle= NULL, $pHttpStatus = 200 ) {
global $gBitSmarty, $gBitThemes;
$pErrorTitle = $this->getConfig( 'site_error_title', '' );
if( empty( $pTemplate ) ) {
$pTemplate = 'error.tpl';
$gBitSmarty->assign( 'fatalTitle', tra( $pErrorTitle ) );
$gBitSmarty->assign( 'msg', $pMsg );
// if mHttpStatus is set, we can assume this was an expected fatal, such as a 404 or 403
error_log( "Fatal Error: $pMsg http://". $_SERVER['HTTP_HOST']. $_SERVER['REQUEST_URI'] );
if( $gBitThemes->isAjaxRequest() ) {
$gBitSmarty->display( 'bitpackage:kernel/'. $pTemplate );
$gBitSmarty->assign( 'metaNoIndex', 1 );
* @param string $ pkgDir = Directory Name of package to load
* @param string $ pScanFile file to be looked for
* @param string $ autoRegister - TRUE = autoregister any packages that don't register on their own, FALSE = don't
* @param string $ pOnce - TRUE = do include_once to load file FALSE = do include to load the file
function loadPackage( $pPkgDir, $pScanFile, $pAutoRegister= TRUE, $pOnce= TRUE ) {
#check if already loaded, loading again won't work with 'include_once' since
#no register call will be done, so don't auto register.
global $gBitSystem, $gLibertySystem, $gBitSmarty, $gBitUser, $gBitLanguage;
include_once( $scanFile );
if( ( $file_exists || $pPkgDir == 'kernel' ) && ( $pAutoRegister && !$this->mRegisterCalled ) ) {
#for auto registered packages Registration Package Name = Package Directory Name
'package_name' => $pPkgDir,
if( $pPkgDir == 'kernel' ) {
$registerHash = array_merge( $registerHash, array( 'required_package'=> TRUE ) );
* scan all available packages. This is an *expensive* function. DO NOT call this functionally regularly , or arbitrarily. Failure to comply is punishable by death by jello suffication!
* @param string $ pScanFile file to be looked for
* @param string $ pOnce - TRUE = do include_once to load file FALSE = do include to load the file
* @param string $ pSelect - empty or 'all' = load all packages, 'installed' = load installed, 'active' = load active, 'x' = load packages with status x
* @param string $ autoRegister - TRUE = autoregister any packages that don't register on their own, FALSE = don't
* @param string $ fileSystemScan - TRUE = scan file system for packages to load, False = don't
* Packages have three different names:
* The directory name where they reside on disk
* The Name they register themselves as when they call registerPackage
* The Key for the array $this->mPackages
* A package in directory 'stars' that registers itself with a name of 'Star Ratings'
* would have these three names:
* Directory Name: 'stars'
* Registered Name: Star Ratings'
* $this->mPackages key: 'star_ratings'
* Of course, its possible for all three names to be the same if the registered name
* is all lower case without spaces and is the same as the diretory name.
* Functions that expect a package name as a parameter should make clear which form
* of the name they expect.
function scanPackages( $pScanFile = 'bit_setup_inc.php', $pOnce= TRUE, $pSelect= '', $pAutoRegister= TRUE ) {
if( !empty( $gPreScan ) && is_array( $gPreScan )) {
// gPreScan may hold a list of packages that must be loaded first
foreach( $gPreScan as $pkgDir ) {
while( FALSE !== ( $dirName = readdir( $pkgDir ))) {
// load the list of pkgs in the right order
foreach( $loadPkgs as $loadPkg ) {
$this->loadPackage( $loadPkg, $pScanFile, $pAutoRegister, $pOnce );
define( 'BIT_STYLES_URL', THEMES_PKG_URL. 'styles/' );
* @return URL of site homepage
* Returns the page for the given type
* defaults to the site homepage
* @return URL of page by index type
global $userlib, $gBitUser, $gBitSystem;
$pIndexType = !is_null( $pIndexType )? $pIndexType : $this->getConfig( "bit_index" );
if( $pIndexType == 'role_home') {
// See if we have first a user assigned default group id, and second a group default system preference
if( !$gBitUser->isRegistered() && ( $role_home = $gBitUser->getHomeRole( ANONYMOUS_TEAM_ID ))) {
} elseif( @$this->verifyId( $gBitUser->mInfo['default_role_id'] ) && ( $role_home = $gBitUser->getHomeRole( $gBitUser->mInfo['default_role_id'] ))) {
} elseif( $this->getConfig( 'default_home_role' ) && ( $role_home = $gBitUser->getHomeRole( $this->getConfig( 'default_home_role' )))) {
if( !empty( $role_home )) {
$url = BIT_ROOT_URL. "index.php". ( !empty( $role_home ) ? "?content_id=". $role_home : "" );
// wiki dependence - NO bad idea
// } elseif( strpos( $group_home, '/' ) === FALSE ) {
// $url = BitPage::getDisplayUrl( $group_home );
} elseif( strpos( $role_home, 'http://' ) === FALSE ){
} elseif( $pIndexType == 'group_home') {
// See if we have first a user assigned default group id, and second a group default system preference
if( !$gBitUser->isRegistered() && ( $group_home = $gBitUser->getGroupHome( ANONYMOUS_GROUP_ID ))) {
} elseif( @$this->verifyId( $gBitUser->mInfo['default_group_id'] ) && ( $group_home = $gBitUser->getGroupHome( $gBitUser->mInfo['default_group_id'] ))) {
} elseif( $this->getConfig( 'default_home_group' ) && ( $group_home = $gBitUser->getGroupHome( $this->getConfig( 'default_home_group' )))) {
if( !empty( $group_home )) {
$url = BIT_ROOT_URL. "index.php". ( !empty( $group_home ) ? "?content_id=". $group_home : "" );
// wiki dependence - NO bad idea
// } elseif( strpos( $group_home, '/' ) === FALSE ) {
// $url = BitPage::getDisplayUrl( $group_home );
} elseif( strpos( $group_home, 'http://' ) === FALSE ){
} elseif( $pIndexType == 'my_page' || $pIndexType == 'my_home' || $pIndexType == 'user_home' ) {
// TODO: my_home is deprecated, but was the default for BWR1. remove in DILLINGER - spiderr
if( $gBitUser->isRegistered() ) {
if( !$gBitUser->isRegistered() ) {
$url = USERS_PKG_URL. 'login.php';
if( $pIndexType == 'my_page' ) {
$url = $gBitSystem->getConfig( 'users_login_homepage', USERS_PKG_URL. 'my.php' );
if( $url != USERS_PKG_URL. 'my.php' && strpos( $url, 'http://' ) === FALSE ){
// the safe assumption is that a custom path is a subpath of the site
// append the root url unless we have a fully qualified uri
} elseif( $pIndexType == 'user_home' ) {
$url = $gBitUser->getDisplayUrl();
$users_homepage = $gBitUser->getPreference( 'users_homepage' );
if( isset ( $users_homepage ) && !empty( $users_homepage )) {
if( strpos($users_homepage, '/') === FALSE ) {
$url = USERS_PKG_URL . 'login.php';
/* this was commented out with the note that this can send requests to inactive packages -
* that should only happen if the admin chooses to point to an inactive pacakge.
* commenting this out however completely breaks the custom uri home page feature, so its
* turned back on and caviate admin - if the problem is more severe than it seems then
* get in touch on irc and we'll work out a better solution than commenting things on and off -wjames5
} elseif( !empty( $pIndexType ) ) {
// if no special case was matched above, default to users' my page
} elseif( !$gBitUser->isRegistered() ) {
$url = USERS_PKG_URL . 'login.php';
$url = USERS_PKG_URL . 'my.php';
if( strpos( $url, 'http://' ) === FALSE ) {
* add javascript to the <body onload> attribute
* @param string $pJavascript javascript to be added
* add javascript to the <body onunload> attribute
* @param string $pJavascript javascript to be added
* get the title of the browser
* set the title of the browser
* @param string $ pTitle title to be used
global $gBitSmarty, $gPageTitle;
$gBitSmarty->assign( 'browserTitle', $pTitle );
$gBitSmarty->assign( 'gPageTitle', $pTitle );
* set the canonical page title
* @param string $ pTitle title to be used
$gBitSmarty->assign( 'canonicalLink', $baseUri. $pRelativeUrl );
$consonantes = "bcdfghjklmnpqrstvwxyz123456789";
for( $i = 0; $i < 8; $i++ ) {
$r .= $vocales{rand( 0, strlen( $vocales ) - 1 )};
$r .= $consonantes{rand( 0, strlen( $consonantes ) - 1 )};
* given an extension, return the mime type
* @param string $pExtension is the extension of the file or the complete file name
* @return mime type of entry and populates $this->mMimeTypes with existing mime types
if( preg_match( "#\.[0-9a-z]+$#i", $pExtension )) {
$pExtension = substr( $pExtension, ( strrpos( $pExtension, '.' ) + 1 ));
// rfc1341 - mime types are case insensitive.
return( !empty( $this->mMimeTypes[$pExtension] ) ? $this->mMimeTypes[$pExtension] : 'application/binary' );
* given an extension, return the mime type
* @param string $pExtension is the extension of the file or the complete file name
* @return mime type of entry and populates $this->mMimeTypes with existing mime types
if( empty( $this->mMimeTypes )) {
// use bitweavers mime.types file to ensure everyone has our set unless user forces his own.
$this->mMimeTypes = array();
if( $fp = fopen( $mimeFile,"r" ) ) {
while( FALSE != ( $line = fgets( $fp, 4096 ) ) ) {
if( !preg_match( "/^\s*(?!#)\s*(\S+)\s+(?=\S)(.+)/", $line, $match ) ) {
foreach( $tmp as $type ) {
$this->mMimeTypes[strtolower( $type )] = $match[1];
// === verifyFileExtension
* given a file and optionally desired name, return the correctly extensioned file and mime type
* @param string $pFile is the actual file to inspect for magic numbers to determine type
* @param string $pFileName is the desired name the file. This is optional in the even the pFile is non-extensioned, as is the case with file uploads
* @return corrected file name and mime type
if( empty( $pFileName ) ) {
$extension = substr( $pFileName, strrpos( $pFileName, '.' ) + 1 );
// extensionless file uploaded, get ready to add an extension
// if $verifyMime turns out to be 'octet-stream' and the lookupMimeType is a video file, we'll allow the video filetype and extenstion
// if we don't do this, most uploaded videos are changed to have a '.bin' extenstion which is very annoying.
if( $verifyMime == 'application/octet-stream' && preg_match( "/^video/", $lookupMime )) {
$verifyMime = $lookupMime;
} elseif( $lookupMime != $verifyMime ) {
$ret = substr( $pFileName, 0, strrpos( $pFileName, '.' ) + 1 ). $verifyExt;
// if we still don't have an extension, we'll simply append a 'bin'
return array( $ret, $verifyMime );
* given a file, return the mime type
* @param string $pExtension is the extension of the file or the complete file name
* @return mime type of entry and populates $this->mMimeTypes with existing mime types
$finfo = finfo_open( FILEINFO_MIME, PHP_MAGIC_PATH );
$finfo = finfo_open( FILEINFO_MIME );
$mime = finfo_file( $finfo, $pFile );
if( $len = strpos( $mime, ';' )) {
$mime = substr( $mime, 0, $len );
if( $pMimeType == 'image/jpeg' ) {
$ret = 'jpg'; // jpeg has three options, .jpg is the most common
if( !($ret = array_search( $pMimeType, $this->mMimeTypes )) ) {
// not present in mMimeTypes, here are some custom types
case 'image/vnd.adobe.photoshop':
* * Prepend $pPath to the include path
include_once( UTIL_PKG_PATH . "PHP_Compat/Compat/Function/get_include_path.php" );
if( !defined( "PATH_SEPARATOR" ) ) {
include_once( UTIL_PKG_PATH . "PHP_Compat/Compat/Constant/PATH_SEPARATOR.php" );
include_once( UTIL_PKG_PATH . "PHP_Compat/Compat/Function/set_include_path.php" );
$include_path = $pPath . PATH_SEPARATOR . $include_path;
* * Append $pPath to the include path
include_once(UTIL_PKG_PATH . "PHP_Compat/Compat/Function/get_include_path.php");
if( !defined("PATH_SEPARATOR" ) ) {
include_once(UTIL_PKG_PATH . "PHP_Compat/Compat/Constant/PATH_SEPARATOR.php");
include_once(UTIL_PKG_PATH . "PHP_Compat/Compat/Function/set_include_path.php");
$include_path .= PATH_SEPARATOR . $pPath;
private function defineTempDir() {
// allow for overridden TEMP_PKG_PATH
if( !defined( 'TEMP_PKG_PATH' ) ) {
$tempDir = $this->getConfig( 'site_temp_dir', self::getDefaultTempDir() );
define( 'TEMP_PKG_PATH', $tempDir );
mkdir( $tempDir, 0777, TRUE );
/* Check that everything is set up properly
static $checked, $gTempDirs;
/* this seems to prevent bw from running on servers where sessions work perfectly,
yet /var/lib/php/ is writeable only by php, not by bw (which is better)
it seems to be enough to set temp in config/kernel/config_inc.php for a writable dir
if session *actually* don't work - other problem
the installer has similar code which is also not used anymore
if( ini_get( 'session.save_handler' ) == 'files' ) {
$save_path = ini_get( 'session.save_path' );
if( empty( $save_path ) ) {
$errors .= "The session.save_path variable is not setup correctly (its empty).\n";
if( strpos( $save_path, ";" ) !== FALSE ) {
$save_path = substr( $save_path, strpos( $save_path, ";" )+1 );
$open = ini_get( 'open_basedir' );
if( !@is_dir( $save_path ) && empty( $open ) ) {
$errors .= "The directory '$save_path' does not exist or PHP is not allowed to access it (check open_basedir entry in php.ini).\n";
} elseif( !bw_is_writeable( $save_path ) ) {
$errors .= "The directory '$save_path' is not writeable.\n";
$save_path = get_temp_dir();
if( is_dir( $save_path ) && bw_is_writeable( $save_path ) ) {
ini_set( 'session.save_path', $save_path );
if( strpos( $_SERVER["SERVER_SOFTWARE"],"IIS" ) && isset ( $_SERVER['COMPUTERNAME'] ) ) {
$wwwuser = 'IUSR_'. $_SERVER['COMPUTERNAME'];
$wwwgroup = 'IUSR_'. $_SERVER['COMPUTERNAME'];
$wwwuser = $userhash ? $userhash['name'] : FALSE;
$wwwgroup = $group ? $group['name'] : FALSE;
$wwwuser = 'nobody (or the user account the web server is running under)';
$wwwgroup = 'nobody (or the group account the web server is running under)';
$this->setConfig( 'site_temp_dir', TEMP_PKG_PATH );
$permFiles[] = TEMP_PKG_PATH;
$permFiles[] = $this->getConfig( 'site_temp_dir', self::getDefaultTempDir() );
foreach( $permFiles as $file ) {
// Create directories as needed
mkdir( $target, 02775, TRUE );
// Check again and report problems
<p>The directory <strong style='color:red;'>$target</strong> does not exist. To create the directory, execute a command such as:</p>
<pre>\$ mkdir -m 777 $target</pre>
$errors .= "<p>The directory <strong style='color:red;'>$target</strong> does not exist. Create the directory $target before proceeding</p>";
$errors .= "<p>The file <b style='color:red;'>$target</b> does not exist. To create the file, execute a command such as:</p>
$errors .= "<p>The file <b style='color:red;'>$target</b> does not exist. Create a blank file $target before proceeding</p>";
// chmod( $target, 02775 );
$errors .= "<p><strong style='color:red;'>$target</strong> is not writeable by $wwwuser. To give $wwwuser write permission, execute a command such as:</p>
<pre>\$ chmod 777 $target</pre>";
$errors .= "<p><b style='color:red;'>$target</b> is not writeable by $wwwuser. Check the security of the file $target before proceeding</p>";
//if (!is_dir("$docroot/$dir")) {
// $errors .= "The directory '$docroot$dir' does not exist.\n";
//} else if (!bw_is_writeable("$docroot/$dir")) {
// $errors .= "The directory '$docroot$dir' is not writeable by $wwwuser.\n";
$PHP_CONFIG_FILE_PATH = PHP_CONFIG_FILE_PATH;
$httpd_conf = 'httpd.conf';
$httpd_conf = $m[1] . '/' . $httpd_conf;
print "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\">
<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">
<title>bitweaver setup problems</title>
<meta http-equiv=\"Pragma\" content=\"no-cache\" />
<meta http-equiv=\"Expires\" content=\"-1\" />
<h1 style=\"color:red;\">bitweaver is not properly set up:</h1>
print "<p>Proceed to the installer <strong>at <a href=\"". BIT_ROOT_URL. "install/install.php\">". BIT_ROOT_URL. "install/install.php</a></strong> after you run the command.";
print "<p>Proceed to the installer <strong>at <a href=\"". BIT_ROOT_URL. "install/install.php\">". BIT_ROOT_URL. "install/install.php</a></strong> after you have corrected the identified problems.";
print "<br />Consult the bitweaver <a href='http://www.bitweaver.org/wiki/index.php?page=Technical_Documentation'>Technical Documentation</a> if you need more help.</p></body></html>";
* isLive returns status of the IS_LIVE constant from config/kernel/config_inc.php
* @return TRUE if IS_LIVE is defined and set to a non empty value, else FALSE
// {{{=========================== Installer related methods ==============================
// Keep these methods in BitSystem that we can call verifyInstalledPackages() and other
// mthods without the need for an install/ package to be present.
* @param array $pTableName
* @param array $pDataDict
* @param array $pRequired
* @param array $pTableOptions
function registerSchemaTable( $pPackage, $pTableName, $pDataDict, $pRequired= FALSE, $pTableOptions= NULL ) {
$pPackage = strtolower( $pPackage ); // lower case for uniformity
if( !empty( $pTableName ) ) {
$this->mPackages[$pPackage]['tables'][$pTableName] = $pDataDict;
if( !empty( $pTableOptions ) ) {
$this->mPackages[$pPackage]['tables']['options'][$pTableName] = $pTableOptions;
* registerSchemaConstraints
* @param array $pTableName
* @param array $pConstraints
if( !empty( $pTableName ) ) {
$this->mPackages[$pPackage]['constraints'][$pTableName] = $pConstraints;
* registerUserPermissions
* @param array $pPackagedir
* @param array $pUserpermissions
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
foreach( $pUserpermissions as $perm ) {
$this->mPermHash[$perm[0]] = $perm;
$this->mPermHash[$perm[0]]['sql'] = "INSERT INTO `". BIT_DB_PREFIX. "users_permissions` (`perm_name`, `perm_desc`, `perm_level`, `package`) VALUES ('$perm[0]', '$perm[1]', '$perm[2]', '$perm[3]')";
"INSERT INTO `". BIT_DB_PREFIX. "users_permissions` (`perm_name`, `perm_desc`, `perm_level`, `package`) VALUES ('$perm[0]', '$perm[1]', '$perm[2]', '$perm[3]')");
* @param array $pPackagedir
* @param array $pPreferences
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
foreach( $pPreferences as $pref ) {
"INSERT INTO `". BIT_DB_PREFIX. "kernel_config`(`package`,`config_name`,`config_value`) VALUES ('$pref[0]', '$pref[1]','$pref[2]')");
* @param array $pPackagedir
* @param array $pPreferences
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
foreach( $pPreferences as $prefHash ) {
$this->mPackages[$pPackagedir]['default_prefs'][] = array( 'package' => $prefHash[0], 'name' => $prefHash[1], 'value' => $prefHash[2] );
* @param array $pModuleHash
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
* @param string $pPackageName the package name
* @param hash $pClassesHash [$className => $pathToClassFile]
* @param array $pInfoHash
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
$pPackage = strtolower( $pPackage ); // lower case for uniformity
if( !empty( $this->mPackages[$pPackage]['info'] )) {
$this->mPackages[$pPackage]['info'] = $pInfoHash;
if( $this->mPackages[$pPackage]['installed'] ) {
$this->mPackages[$pPackage]['info']['upgrade'] = $upgrade;
$this->mPackages[$pPackage]['info']['version'] = $upgrade;
* registerSchemaSequences
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
$pPackage = strtolower( $pPackage ); // lower case for uniformity
$this->mPackages[$pPackage]['sequences'] = $pSeqHash;
* @param array $pIndexHash
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
$pPackage = strtolower( $pPackage ); // lower case for uniformity
$this->mPackages[$pPackage]['indexes'] = $pIndexHash;
* @param array $pMixedDefaultSql
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
$pPackage = strtolower( $pPackage ); // lower case for uniformity
if( empty( $this->mPackages[$pPackage]['defaults'] ) ) {
$this->mPackages[$pPackage]['defaults'] = array();
foreach( $pMixedDefaultSql as $def ) {
$this->mPackages[$pPackage]['defaults'][] = $def;
* storeVersion will store the version number of a given package
* @param array $pPackage Name of package - if not given, bitweaver_version will be stored
* @param array $pVersion Version number
* @return TRUE on success, FALSE on failure
if( empty( $pPackage )) {
$gBitSystem->storeConfig( "bitweaver_version", $pVersion, 'kernel' );
} elseif( !empty( $gBitSystem->mPackages[$pPackage] )) {
$gBitSystem->storeConfig( "package_". $pPackage. "_version", $pVersion, $pPackage );
* getVersion will fetch the version number of a given package
* @param array $pPackage Name of package - if not given, bitweaver_version will be stored
* @param array $pVersion Version number
* @return version number on success
function getVersion( $pPackage = NULL, $pDefault = '0.0.0' ) {
if( empty( $pPackage )) {
$config = 'bitweaver_version';
$config = "package_". $pPackage. "_version";
return $gBitSystem->getConfig( $config, $pDefault );
* getLatestUpgradeVersion will fetch the greatest upgrade number for a given package
* @param array $pPackage package we want to fetch the latest version number for
* @return string greatest upgrade number for a given package
if( !empty( $pPackage )) {
while( FALSE !== ( $file = readdir( $upDir ))) {
// we only want to update $ret if the version of the file is greater than the previous one
return(( $ret == '0.0.0' ) ? FALSE : $ret );
* registerPackageVersion Holds the package version
$this->mPackages[$pPackage]['version'] = $pVersion;
* @return TRUE on success, FALSE on failure
return( preg_match( "/^(\d+\.\d+\.\d+)(-dev|-alpha|-beta|-pl|-RC\d+)?$/", $pVersion ));
$this->mRequirements[$pPackage] = $pReqHash;
// and we display the info
$this->mPackages[$pPackage]['info']['requirements'] = '';
foreach( $pReqHash as $req => $version ) {
//$this->mPackages[$req]['is_requirement'] = TRUE;
$this->mPackages[$pPackage]['info']['requirements'] .= '<a class="external" href="http://www.bitweaver.org/wiki/'. ucfirst( $req ). 'Package">'. ucfirst( $req ). '</a>';
$max = ( !empty( $version['max'] ) ? " - ". $version['max'] : '' );
if( $version['min'] != '0.0.0' ) {
$this->mPackages[$pPackage]['info']['requirements'] .= " (". $version['min']. $max. ")";
$this->mPackages[$pPackage]['info']['requirements'] .= ", ";
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
if( !empty( $pReqHash ) && is_array( $pReqHash )) {
foreach( $pReqHash as $pkg => $versions ) {
if( empty( $versions['min'] )) {
$this->mErrors['version_min'] = "You have to provide a minimum version number for the $pkg requirement. If you just want the required package to be present, please use 0.0.0 as minimum version.";
$this->mErrors['version_min'] = "Please make sure you use a valid minimum version number for the $pkg requirement.";
} elseif( !empty( $versions['max'] )) {
$this->mErrors['version_max'] = "Please make sure you use a valid maximum version number for the $pkg requirement.";
$this->mErrors['version_max'] = "Please make sure the maximum version is greater than the minimum version for the $pkg requirement.";
$this->mErrors['deps'] = "If you want to register requirements, please do so with a valid requirement hash.";
// since this should only show up when devs are working, we'll simply display the output:
* @return array of package requirements
if( !empty( $pPackage )) {
if( !empty( $this->mRequirements[$pPackage] )) {
return $this->mRequirements[$pPackage];
* calculateRequirements will calculate all requirements and return a hash of the results
* @param boolean $pInstallVersion Use the actual installed version instead of the version that will be in bitweaver after the upgrade
* @return boolean TRUE on success, FALSE on failure - mErrors will contain reason for failure
// first we gather all version information.
// get the latest upgrade version, since this is the version the package will be at after install
$installed[$package] = $version;
$requirements[$package] = $deps;
$inactive[$package] = FALSE;
$inactive[$package] = TRUE;
if( !empty( $requirements )) {
foreach( $requirements as $package => $deps ) {
foreach( $deps as $depPackage => $depVersion ) {
'package_version' => $installed[$package],
'requires' => $depPackage,
'required_version' => $depVersion,
if( !empty( $installed[$depPackage] )) {
$hash['version']['available'] = $installed[$depPackage];
if( empty( $installed[$depPackage] )) {
$hash['result'] = 'missing';
} elseif( version_compare( $depVersion['min'], $installed[$depPackage], '>' )) {
$hash['result'] = 'min_dep';
} elseif( !empty( $depVersion['max'] ) && version_compare( $depVersion['max'], $installed[$depPackage], '<' )) {
$hash['result'] = 'max_dep';
} elseif( isset ( $inactive[$depPackage] ) && $inactive[$depPackage] ) {
$hash['result'] = 'inactive';
* drawRequirementsGraph Will draw a requirement graph if PEAR::Image_GraphViz is installed
* @param boolean $pInstallVersion Use the actual installed version instead of the version that will be in bitweaver after the upgrade
* @param string $pFormat dot output format
* @param string $pCommand dot or neato
* @return boolean TRUE on success, FALSE on failure - mErrors will contain reason for failure
global $gBitSmarty, $gBitThemes;
// only do this if we can load PEAR GraphViz interface
if( @include_once( 'Image/GraphViz.php' )) {
$delKeys = $matches = array();
// crazy manipulation of hash to remove duplicate version matches.
// we do this that we can use double headed arrows in the graph below.
foreach( $deps as $key => $req ) {
foreach( $deps as $k => $d ) {
if( $req['requires'] == $d['package'] && $req['package'] == $d['requires'] && $req['result'] == 'ok' && $d['result'] == 'ok' ) {
$deps[$key]['dir'] = 'both';
foreach( $matches as $key => $match ) {
unset ( $delKeys[$match] );
// remove dupes from hash
foreach( $delKeys as $key ) {
$graph = new Image_GraphViz( TRUE, $gBitThemes->getGraphvizGraphAttributes(), 'Requirements', TRUE );
$fromattributes = $toattributes = $gBitThemes->getGraphvizNodeAttributes();
foreach( $deps as $node ) {
//$fromNode = ucfirst( $node['package'] )."\n".$node['package_version'];
//$toNode = ucfirst( $node['requires'] )."\n".$node['required_version']['min'];
$fromNode = ucfirst( $node['package'] );
$toNode = ucfirst( $node['requires'] );
switch( $node['result'] ) {
$edgecolor = 'chocolate3';
$label = 'Maximum version\nexceeded';
$toNode .= "\n". $node['required_version']['min']. " - ". $node['required_version']['max'];
$toattributes['fillcolor'] = 'khaki';
$label = 'Minimum version\nnot met';
$toNode .= "\n". $node['required_version']['min'];
if( !empty( $node['required_version']['max'] )) {
$toNode .= " - ". $node['required_version']['max'];
$toattributes['fillcolor'] = 'pink';
$label = 'Not installed\nor activated';
$toNode .= "\n". $node['required_version']['min'];
if( !empty( $node['required_version']['max'] )) {
$toNode .= " - ". $node['required_version']['max'];
$toattributes['fillcolor'] = 'pink';
$toattributes['fillcolor'] = 'white';
$fromattributes['URL'] = "http://www.bitweaver.org/wiki/". ucfirst( $node['package'] ). "Package";
$graph->addNode( $fromNode, $fromattributes );
$toattributes['URL'] = "http://www.bitweaver.org/wiki/". ucfirst( $node['requires'] ). "Package";
$graph->addNode( $toNode, $toattributes );
array( $fromNode => $toNode ),
$gBitThemes->getGraphvizEdgeAttributes( array(
'dir' => ( !empty( $node['dir'] ) ? $node['dir'] : '' ),
'fontcolor' => $edgecolor,
if( preg_match( "#(png|gif|jpe?g|bmp|svg|tif)#i", $pFormat )) {
$graph->image( $pFormat, $pCommand );
return $graph->fetch( $pFormat, $pCommand );
* verifyInstalledPackages scan all available packages
* @param string $ pScanFile file to be looked for
#load in any admin/schema_inc.php files that exist for each package
$this->scanPackages( 'admin/schema_inc.php', TRUE, $pSelect, FALSE, TRUE );
if( $lastQuote != FALSE ) {
$showTables = ( $prefix ? $prefix. '%' : NULL );
if( $dbTables = $this->mDb->MetaTables( 'TABLES', FALSE, $showTables ) ) {
// make a copy that we can keep track of what tables have been used
$unusedTables = $dbTables;
// Default to TRUE, &= will FALSE out
$this->mPackages[$package]['installed'] = TRUE;
if( !empty( $this->mPackages[$package]['tables'] ) ) {
$this->mPackages[$package]['db_tables_found'] = TRUE;
// painful hardcoded exception for bitcommerce
if( $package == 'bitcommerce' ) {
$fullTable = $prefix. $table;
$tablePresent = in_array( $fullTable, $dbTables );
$ret['present'][$package][] = $table;
$ret['missing'][$package][] = $table;
// This is a crude but highly effective means of blurting out a very bad situation when an installed package is missing a table
// This needs hiding during install so disabled for now
// vd( "Table Missing => $package : $table" );
// lets also return the tables that are not in use by bitweaver
// this is useful when we want to remove old tables or upgrade tables
if(( $key = array_search( $fullTable, $dbTables )) !== FALSE ) {
unset ( $unusedTables[$key] );
$this->mPackages[$package]['installed'] &= $tablePresent;
$this->mPackages[$package]['db_tables_found'] &= $tablePresent;
$this->mPackages[$package]['db_tables_found'] = FALSE;
$this->mPackages[$package]['installed'] = FALSE;
if( !empty( $this->mPackages[$package]['required'] ) && $this->mPackages[$package]['active_switch'] != 'y' ) {
// we have a disabled required package. turn it back on!
$this->storeConfig( 'package_' . $package, 'y', $package );
} elseif( !empty( $this->mPackages[$package]['required'] ) && $this->mPackages[$package]['installed'] && $this->getConfig( 'package_'. $package ) != 'i' && $this->getConfig( 'package_'. $package ) != 'y' ) {
$this->storeConfig( 'package_' . $package, 'i', $package );
// set package to i if it is installed but not isFeatureActive (common when re-installing packages)
$this->storeConfig( 'package_' . $package, 'i', $package );
$this->mPackages[$package]['info']['version'] = $version;
unset ( $this->mPackages[$package]['info']['upgrade'] );
$ret['unused'] = $unusedTables;
// {{{=========================== Date and time methods ==============================
* Retrieve a current UTC timestamp
* Simple map to BitDate object allowing tidy display elsewhere
return $this->mServerTimestamp->getUTCTime();
* Retrieve a current UTC ISO timestamp
* Simple map to BitDate object allowing tidy display elsewhere
return $this->mServerTimestamp->getUTCTimestamp();
* Retrieves the user's preferred offset for displaying dates.
return $this->mServerTimestamp->get_display_offset( $pUser );
* Retrieves the user's preferred long date format for displaying dates.
static $site_long_date_format = FALSE;
if( !$site_long_date_format ) {
$site_long_date_format = $this->getConfig( 'site_long_date_format', '%A %d of %B, %Y' );
return $site_long_date_format;
* Retrieves the user's preferred short date format for displaying dates.
static $site_short_date_format = FALSE;
if( !$site_short_date_format ) {
$site_short_date_format = $this->getConfig( 'site_short_date_format', '%d %b %Y' );
return $site_short_date_format;
* Retrieves the user's preferred long time format for displaying dates.
static $site_long_time_format = FALSE;
if( !$site_long_time_format ) {
$site_long_time_format = $this->getConfig( 'site_long_time_format', '%H:%M:%S %Z' );
return $site_long_time_format;
* Retrieves the user's preferred short time format for displaying dates.
static $site_short_time_format = FALSE;
if( !$site_short_time_format ) {
$site_short_time_format = $this->getConfig( 'site_short_time_format', '%H:%M %Z' );
return $site_short_time_format;
* Retrieves the user's preferred long date/time format for displaying dates.
static $long_datetime_format = FALSE;
if( !$long_datetime_format ) {
$long_datetime_format = $this->getConfig( 'site_long_datetime_format', '%A %d of %B, %Y (%H:%M:%S %Z)' );
return $long_datetime_format;
* Retrieves the user's preferred short date/time format for displaying dates.
static $short_datetime_format = FALSE;
if( !$short_datetime_format ) {
$short_datetime_format = $this->getConfig( 'site_short_datetime_format', '%a %d of %b, %Y (%H:%M %Z)' );
return $short_datetime_format;
* Only used in rang_lib.php which needs tidying up to use smarty templates
* getBitVersion will fetch the version of bitweaver as set in kernel/config_defaults_inc.php
* @param boolean $pIncludeLevel Return bitweaver version including BIT_LEVEL
* @return string bitweaver version set in kernel/config_defaults_inc.php
* checkBitVersion Check for new version of bitweaver
* @return returns an array with information on bitweaver version
$error['string'] = $data = '';
// cache the bitversion.txt file locally and update only once a day
// if you don't have a connection to bitweaver.org, you can set a cronjob to 'touch' this file once a day to avoid waiting for a timeout.
if( !is_file( TEMP_PKG_PATH. 'bitversion.txt' ) || ( time() - filemtime( TEMP_PKG_PATH. 'bitversion.txt' )) > 86400 ) {
if( $h = fopen( TEMP_PKG_PATH. 'bitversion.txt', 'w' )) {
$h = fopen( TEMP_PKG_PATH. 'bitversion.txt', 'r' );
$data = fread( $h, 1024 );
// nuke all lines that don't just contain a version number
foreach( $lines as $line ) {
if( !empty( $data ) && !empty( $versions ) && preg_match( "/\d+\.\d+\.\d+/", $versions[0] ) ) {
foreach( $versions as $version ) {
$ret['upgrade'] = $version;
// check if there have been any major releases
$ret['release'] = implode( '.', $release );
$ret['page'] = $release[0]. '.'. $release[1];
$ret['upgrade'] = $version;
$error['string'] = tra( 'No version information available. Check your connection to bitweaver.org' );
// append any release level
// should be moved somewhere else. unbreaking things for now - 25-JUN-2005 - spiderr
// \TODO remove html hardcoded in diff2
function diff2( $page1, $page2 ) {
$page1 = split( "\n", $page1 );
$page2 = split( "\n", $page2 );
$z = new WikiDiff( $page1, $page2 );
$html = '<hr /><br />['. tra("Versions are identical"). ']<br /><br />';
//$fmt = new WikiDiffFormatter;
$fmt = new WikiUnifiedDiffFormatter;
$html = $fmt->format( $z, $page1 );
* getIncludeFiles will get a set of available files with a given filename
* @param array $pPhpFile name of php file
* @param array $pTplFile name of tpl file
* @return array of includable files
foreach( $gBitSystem->mPackages as $package ) {
if( !empty( $pPhpFile )) {
$php_file = $package['path']. $pPhpFile;
$ret[$package['name']]['php'] = $php_file;
if( !empty( $pTplFile )) {
$tpl_file = $package['path']. 'templates/'. $pTplFile;
$ret[$package['name']]['tpl'] = 'bitpackage:'. $package['name']. '/'. $pTplFile;
/* Function for sorting AppMenu by menu_position */
$pa = empty( $a['menu_position'] ) ? 0 : $a['menu_position'];
$pb = empty( $b['menu_position'] ) ? 0 : $b['menu_position'];
if( $pa == 0 && $pb == 0 ) {
return( strcmp( $pb['menu_title'], $pa['menu_title'] ));
/* vim: :set fdm=marker : */
|