PageRenderTime 273ms CodeModel.GetById 181ms app.highlight 44ms RepoModel.GetById 36ms app.codeStats 1ms

/web/concrete/core/models/block_types.php

https://github.com/glockops/concrete5
PHP | 1046 lines | 639 code | 136 blank | 271 comment | 107 complexity | 05f4afeb5aca3379d14a77d9ebb85cc0 MD5 | raw file
   1<?
   2
   3defined('C5_EXECUTE') or die("Access Denied.");
   4
   5/**
   6 * Contains the blocktype object, the block type list (which is just a wrapper for querying the system for block types, and the block type
   7 * DB wrapper for ADODB.
   8 * @package Blocks
   9 * @author Andrew Embler <andrew@concrete5.org>
  10 * @category Concrete
  11 * @copyright  Copyright (c) 2003-2008 Concrete5. (http://www.concrete5.org)
  12 * @license    http://www.concrete5.org/license/     MIT License
  13 *
  14 */
  15 
  16/**
  17*
  18* The block type list object holds types of blocks, takes care of querying the file system for newly available
  19* @author Andrew Embler <andrew@concrete5.org>
  20* @copyright  Copyright (c) 2003-2008 Concrete5. (http://www.concrete5.org)
  21* @license    http://www.concrete5.org/license/     MIT License
  22* @package Blocks
  23* @category Concrete
  24*/	
  25
  26	class Concrete5_Model_BlockTypeList extends Object {
  27
  28		/**
  29		 * array of BlockType objects - should likely be considered a protected property
  30		 * use getBlockTypeList instead of accessing this property directly
  31		 * @see BlockTypeList::getBlockTypeList()
  32		 * @var BlockType[] $btArray
  33		 */
  34		public $btArray = array();
  35		
  36		/**
  37		 * Gets an array of BlockTypes for a given Package
  38		 * @param Package $pkg
  39		 * @return BlockType[]
  40		 */
  41		public static function getByPackage($pkg) {
  42			$db = Loader::db();
  43			$r = $db->Execute("select btID from BlockTypes where pkgID = ?", $pkg->getPackageID());
  44			$blockTypes = array();
  45			while ($row = $r->FetchRow()) {
  46				$blockTypes[] = BlockType::getByID($row['btID']);
  47			}
  48			return $blockTypes;
  49		}
  50		
  51		
  52		/**
  53		 * @todo comment this one
  54		 * @param string $xml
  55		 * @return void
  56		 */
  57		public static function exportList($xml) {
  58			$attribs = self::getInstalledList();
  59			$nxml = $xml->addChild('blocktypes');
  60			foreach($attribs as $bt) {
  61				$type = $nxml->addChild('blocktype');
  62				$type->addAttribute('handle', $bt->getBlockTypeHandle());
  63				$type->addAttribute('package', $bt->getPackageHandle());
  64			}
  65		}
  66		
  67		/**
  68		 * returns an array of Block Types used in the concrete5 Dashboard
  69		 * @param Area $ap
  70		 * @return BlockType[]
  71		 */
  72		public static function getDashboardBlockTypes() {
  73			$db = Loader::db();
  74			$btIDs = $db->GetCol('select btID from BlockTypes where btHandle like "dashboard_%" order by btDisplayOrder asc, btID asc');
  75			$blockTypes = array();
  76			foreach($btIDs as $btID) {
  77				$blockTypes[] = BlockType::getByID($btID);
  78			}
  79			return $blockTypes;
  80		}
  81		
  82		/**
  83		 * BlockTypeList class constructor
  84		 * @param array $allowedBlocks array of allowed BlockType id's if you'd like to limit the list to just those
  85		 * @return BlockTypeList
  86		 */
  87		function __construct($allowedBlocks = null) {
  88			$db = Loader::db();
  89			$this->btArray = array();
  90						
  91			$q = "select btID from BlockTypes where btIsInternal = 0 ";
  92			if ($allowedBlocks != null) {
  93				$q .= ' and btID in (' . implode(',', $allowedBlocks) . ') ';
  94			}
  95			$q .= ' order by btDisplayOrder asc, btName asc, btID asc';
  96			
  97			$r = $db->query($q);
  98	
  99			if ($r) {
 100				while ($row = $r->fetchRow()) {
 101					$bt = BlockType::getByID($row['btID']);
 102					if (is_object($bt)) {
 103						$this->btArray[] = $bt;
 104					}
 105				}
 106				$r->free();
 107			}
 108											
 109			return $this;
 110		}
 111		
 112		/**
 113		 * gets the array of BlockType objects
 114		 * @return BlockType[]
 115		 * @see BlockTypeList::getInstalledList()
 116		 */
 117		public function getBlockTypeList() {
 118			return $this->btArray;
 119		}
 120
 121		/**
 122		 * Gets a list of block types that are not installed, used to get blocks that can be installed
 123		 * This function only surveys the web/blocks directory - it's not looking at the package level.
 124		 * @return BlockType[] 
 125		 */
 126		public static function getAvailableList() {
 127			$env = Environment::get();
 128			$env->clearOverrideCache();
 129			$blocktypes = array();
 130			$dir = DIR_FILES_BLOCK_TYPES;
 131			$db = Loader::db();
 132			
 133			$btHandles = $db->GetCol("select btHandle from BlockTypes order by btDisplayOrder asc, btName asc, btID asc");
 134			
 135			$aDir = array();
 136			if (is_dir($dir)) {
 137				$currentLocale = Localization::activeLocale();
 138				if ($currentLocale != 'en_US') {
 139					Localization::changeLocale('en_US');
 140				}
 141				$handle = opendir($dir);
 142				while(($file = readdir($handle)) !== false) {
 143					if (strpos($file, '.') === false) {
 144						$fdir = $dir . '/' . $file;
 145						if (is_dir($fdir) && !in_array($file, $btHandles) && file_exists($fdir . '/' . FILENAME_BLOCK_CONTROLLER)) {
 146							$bt = new BlockType;
 147							$bt->btHandle = $file;
 148							$class = $bt->getBlockTypeClass();
 149							
 150							require_once($fdir . '/' . FILENAME_BLOCK_CONTROLLER);
 151							if (!class_exists($class)) {
 152								continue;
 153							}
 154							$bta = new $class;
 155							$bt->btName = $bta->getBlockTypeName();
 156							$bt->btDescription = $bta->getBlockTypeDescription();
 157							$bt->hasCustomViewTemplate = file_exists(DIR_FILES_BLOCK_TYPES . '/' . $file . '/' . FILENAME_BLOCK_VIEW);
 158							$bt->hasCustomEditTemplate = file_exists(DIR_FILES_BLOCK_TYPES . '/' . $file . '/' . FILENAME_BLOCK_EDIT);
 159							$bt->hasCustomAddTemplate = file_exists(DIR_FILES_BLOCK_TYPES . '/' . $file . '/' . FILENAME_BLOCK_ADD);
 160							$bt->installed = false;
 161							$bt->btID = null;
 162							$blocktypes[] = $bt;
 163							
 164						}
 165					}				
 166				}
 167				if ($currentLocale != 'en_US') {
 168					Localization::changeLocale($currentLocale);
 169				}
 170			}
 171			
 172			return $blocktypes;
 173		}
 174
 175		/**
 176		 * gets a list of installed BlockTypes
 177		 * @return BlockType[]
 178		 */	
 179		public static function getInstalledList() {
 180			$db = Loader::db();
 181			$r = $db->query("select btID from BlockTypes order by btDisplayOrder asc, btName asc, btID asc");
 182			$btArray = array();
 183			while ($row = $r->fetchRow()) {
 184				$bt = BlockType::getByID($row['btID']);
 185				if (is_object($bt)) {
 186					$btArray[] = $bt;
 187				}
 188			}
 189			return $btArray;
 190		}
 191		
 192		/**
 193		 * Gets a list of installed BlockTypes 
 194		 * - could be defined as static
 195		 * @todo we have three duplicate functions getBlockTypeArray, getInstalledList, getBlockTypeList
 196		 * @return BlockType[]
 197		 */	
 198		public function getBlockTypeArray() {
 199			$db = Loader::db();
 200			$q = "select btID from BlockTypes order by btDisplayOrder asc, btName asc, btID asc";
 201			$r = $db->query($q);
 202			$btArray = array();
 203			if ($r) {
 204				while ($row = $r->fetchRow()) {
 205					$bt = BlockType::getByID($row['btID']);
 206					if (is_object($bt)) {
 207						$btArray[] = $bt;
 208					}
 209				}
 210				$r->free();
 211			}
 212			return $btArray;
 213		}
 214		
 215		/**
 216		 * gets the form post action for the current block type given the area
 217		 * @param Area $a
 218		 * @return string
 219		 */
 220		public function getBlockTypeAddAction(&$a) {
 221			$step = ($_REQUEST['step']) ? '&step=' . $_REQUEST['step'] : '';
 222			$arHandle = urlencode($a->getAreaHandle());
 223			$c = $a->getAreaCollectionObject();
 224			$cID = $c->getCollectionID();
 225			$valt = Loader::helper('validation/token');
 226			$str = DIR_REL . "/" . DISPATCHER_FILENAME . "?cID={$cID}&amp;areaName={$arHandle}&amp;mode=edit&amp;btask=add" . $step . '&' . $valt->getParameter();
 227			return $str;			
 228		}
 229		
 230		/**
 231		 * gets the form post action for the current block type given the area
 232		 * @param Area $a
 233		 * @return string
 234		 */
 235		public function getBlockTypeAliasAction(&$a) {
 236			$step = ($_REQUEST['step']) ? '&step=' . $_REQUEST['step'] : '';
 237			$arHandle = urlencode($a->getAreaHandle());
 238			$c = $a->getAreaCollectionObject();
 239			$cID = $c->getCollectionID();
 240			$str = DIR_REL . "/" . DISPATCHER_FILENAME . "?cID={$cID}&amp;areaName={$arHandle}&amp;mode=edit&amp;btask=alias" . $step . '&' . $valt->getParameter();
 241			return $str;			
 242		}
 243		
 244		public static function resetBlockTypeDisplayOrder($column = 'btID') {
 245			$db = Loader::db();
 246			$stmt = $db->Prepare("UPDATE BlockTypes SET btDisplayOrder = ? WHERE btID = ?");
 247			$btDisplayOrder = 1;
 248			$blockTypes = $db->GetArray("SELECT btID, btHandle, btIsInternal FROM BlockTypes ORDER BY {$column} ASC");
 249			foreach ($blockTypes as $bt) {
 250				if ($bt['btIsInternal']) {
 251					$db->Execute($stmt, array(0, $bt['btID']));
 252				} else {
 253					$db->Execute($stmt, array($btDisplayOrder, $bt['btID']));
 254					$btDisplayOrder++;
 255				}
 256			}
 257		}
 258		
 259	}
 260
 261/**
 262*
 263* @access private
 264*/	
 265	class Concrete5_Model_BlockTypeDB extends ADOdb_Active_Record {
 266		public $_table = 'BlockTypes';
 267	}
 268
 269/**
 270*
 271* Any type of content that can be added to pages is represented as a type of block, and thereby a block type object.
 272* @package Blocks
 273* @author Andrew Embler <andrew@concrete5.org>
 274* @license    http://www.concrete5.org/license/     MIT License
 275* @package Blocks
 276* @category Concrete
 277*/		
 278	class Concrete5_Model_BlockType extends Object {
 279
 280		/**
 281		 * @var array $addBTUArray
 282		 */
 283		public $addBTUArray = array();
 284		
 285		/**
 286		 * @var array $addBTGArray
 287		 */
 288		public $addBTGArray = array();
 289		
 290		/**
 291		 * @var BlockTypeController
 292		 */
 293		public $controller;
 294		
 295		/**
 296		 * Gets the BlockType object for the given Block Type Handle
 297		 * ex: 
 298		 * <code><?php
 299		 * $bt = BlockType::getByHandle('content'); // returns the BlockType object for the core Content block
 300		 * ?></code>
 301		 * @param string $handle
 302		 * @return BlockType|false
 303		 */
 304		public static function getByHandle($handle) {
 305			$bt = CacheLocal::getEntry('blocktype', $handle);
 306			if ($bt === -1) {
 307				return false;
 308			}
 309
 310			if (is_object($bt)) {
 311				$bt->controller = Loader::controller($bt);
 312				return $bt;
 313			}
 314
 315			$bt = BlockType::get('btHandle = ?', array($handle));
 316			if (is_object($bt)) {
 317				CacheLocal::set('blocktype', $handle, $bt);
 318				$bt->controller = Loader::controller($bt);
 319				return $bt;
 320			}
 321
 322			CacheLocal::set('blocktype', $handle, -1);
 323			return false;
 324		}
 325
 326		/**
 327		 * Gets the BlockType for a given Block Type ID
 328		 * @param int $btID
 329		 * @return BlockType
 330		 */
 331		public static function getByID($btID) {
 332			$bt = CacheLocal::getEntry('blocktype', $btID);
 333			if ($bt === -1) {
 334				return false;
 335			} else if (!is_object($bt)) {
 336				$where = 'btID = ?';
 337				$bt = BlockType::get($where, array($btID));			
 338				if (is_object($bt)) {
 339					CacheLocal::set('blocktype', $btID, $bt);
 340				} else {
 341					CacheLocal::set('blocktype', $btID, -1);
 342				}
 343			}
 344			$bt->controller = Loader::controller($bt);
 345			return $bt;
 346		}
 347		
 348		
 349		/**
 350		 * internal method to query the BlockTypes table and get a BlockType object
 351		 * @param string
 352		 * @param array
 353		 */
 354		protected static function get($where, $properties) {
 355			$db = Loader::db();
 356			
 357			$q = "select btID, btName, btDescription, btHandle, pkgID, btActiveWhenAdded, btIsInternal, btCopyWhenPropagate, btIncludeAll, btDisplayOrder, btInterfaceWidth, btInterfaceHeight from BlockTypes where {$where}";
 358			
 359			$r = $db->query($q, $properties);
 360			
 361			if ($r->numRows() > 0) {
 362				$row = $r->fetchRow();
 363				$bt = new BlockType;
 364				$bt->setPropertiesFromArray($row);
 365				return $bt;
 366			}
 367			
 368		}
 369		
 370		/** 
 371		 * if a the current BlockType is Internal or not - meaning one of the core built-in concrete5 blocks
 372		 * @access private
 373		 * @return boolean
 374		 */
 375		function isBlockTypeInternal() {return $this->btIsInternal;}
 376		
 377		/** 
 378		 * Returns true if the block type is internal (and therefore cannot be removed) a core block
 379		 * @return boolean
 380		 */
 381		public function isInternalBlockType() {
 382			return $this->btIsInternal;
 383		}
 384		
 385		/** 
 386		 * Returns true if the block type ships with concrete5 by checking to see if it's in the concrete/blocks/ directory
 387		 * @deprecated
 388		 */
 389		public function isCoreBlockType() {
 390			return is_dir(DIR_FILES_BLOCK_TYPES_CORE . '/' . $this->getBlockTypeHandle());
 391		}
 392
 393		/**
 394		 * Determines if the block type has templates available
 395		 * @return boolean
 396		 */
 397		public function hasAddTemplate() {
 398			$bv = new BlockView();
 399			$bv->setBlockObject($this);
 400			$path = $bv->getBlockPath(FILENAME_BLOCK_ADD);
 401			if (file_exists($path . '/' . FILENAME_BLOCK_ADD)) {
 402				return true;
 403			}
 404			return false;
 405		}
 406		
 407		
 408		/**
 409		 * returns the width in pixels that the block type's editing dialog will open in
 410		 * @return int
 411		 */
 412		public function getBlockTypeInterfaceWidth() {return $this->btInterfaceWidth;}
 413		
 414		/**
 415		 * returns the height in pixels that the block type's editing dialog will open in
 416		 * @return int
 417		 */
 418		public function getBlockTypeInterfaceHeight() {return $this->btInterfaceHeight;}
 419		
 420		/**
 421		 * returns the id of the BlockType's package if it's in a package
 422		 * @return int
 423		 */
 424		public function getPackageID() {return $this->pkgID;}
 425		
 426		/**
 427		 * returns the handle of the BlockType's package if it's in a package
 428		 * @return string
 429		 */
 430		public function getPackageHandle() {
 431			return PackageList::getHandle($this->pkgID);
 432		}
 433		
 434		
 435		/**
 436		 * determines if a user or group can add a block of the current BlockType
 437		 * @param UserInfo|Group $obj
 438		 * @return boolean
 439		 */
 440		public function canAddBlock($obj) {
 441			switch(strtolower(get_class($obj))) {
 442				case 'group':
 443					return in_array($obj->getGroupID(), $this->addBTGArray);
 444					break;
 445				case 'userinfo':
 446					return in_array($obj->getUserID(), $this->addBTUArray);
 447					break;
 448			}
 449		}
 450		
 451		/** 
 452		 * Returns the number of unique instances of this block throughout the entire site
 453		 * note - this count could include blocks in areas that are no longer rendered by the theme
 454		 * @param boolean specify true if you only want to see the number of blocks in active pages
 455		 * @return int
 456		 */
 457		public function getCount($ignoreUnapprovedVersions = false) {
 458			$db = Loader::db();
 459            		if ($ignoreUnapprovedVersions) {
 460                		$count = $db->GetOne("SELECT count(btID) FROM Blocks b
 461                    			WHERE btID=?
 462                    			AND EXISTS (
 463                        			SELECT 1 FROM CollectionVersionBlocks cvb 
 464                        			INNER JOIN CollectionVersions cv ON cv.cID=cvb.cID AND cv.cvID=cvb.cvID
 465                        			WHERE b.bID=cvb.bID AND cv.cvIsApproved=1
 466                    			)", array($this->btID));            
 467            		}
 468            		else {
 469                		$count = $db->GetOne("SELECT count(btID) FROM Blocks WHERE btID = ?", array($this->btID));
 470            		}
 471			return $count;
 472		}
 473		
 474		/**
 475		 * Not a permissions call. Actually checks to see whether this block is not an internal one.
 476		 * @return boolean
 477		 */
 478		public function canUnInstall() {
 479			/*$cnt = $this->getCount();
 480			if ($cnt > 0 || $this->isBlockTypeInternal()) {
 481				return false;
 482			}*/
 483			
 484			return (!$this->isBlockTypeInternal());
 485		}
 486		
 487		/**
 488		 * gets the BlockTypes description text
 489		 * @return string
 490		 */
 491		function getBlockTypeDescription() {
 492			return $this->btDescription;
 493		}
 494		
 495		/**
 496		 * Gets the custom templates available for the current BlockType
 497		 * @return TemplateFile[]
 498		 */
 499		function getBlockTypeCustomTemplates() {
 500			$btHandle = $this->getBlockTypeHandle();
 501			$fh = Loader::helper('file');
 502			$files = array();
 503			$dir = DIR_FILES_BLOCK_TYPES . "/{$btHandle}/" . DIRNAME_BLOCK_TEMPLATES;
 504			if(is_dir($dir)) {
 505				$files = array_merge($files, $fh->getDirectoryContents($dir));
 506			}
 507			// NOW, we check to see if this btHandle has any custom templates that have been installed as separate packages
 508			foreach(PackageList::get()->getPackages() as $pkg) {
 509				$dir =
 510					(is_dir(DIR_PACKAGES . '/' . $pkg->getPackageHandle()) ? DIR_PACKAGES : DIR_PACKAGES_CORE)
 511					. '/'. $pkg->getPackageHandle() . '/' . DIRNAME_BLOCKS . '/' . $btHandle . '/' . DIRNAME_BLOCK_TEMPLATES
 512				;
 513				if(is_dir($dir)) {
 514					$files = array_merge($files, $fh->getDirectoryContents($dir));
 515				}
 516			}
 517			$dir = DIR_FILES_BLOCK_TYPES_CORE . "/{$btHandle}/" . DIRNAME_BLOCK_TEMPLATES;
 518			if(is_dir($dir)) {
 519				$files = array_merge($files, $fh->getDirectoryContents($dir));
 520			}
 521			Loader::library('template_file');
 522			$templates = array();
 523			foreach(array_unique($files) as $file) {
 524				$templates[] = new TemplateFile($this, $file);
 525			}
 526			return TemplateFile::sortTemplateFileList($templates);
 527		}
 528
 529		/**
 530		 * gets the available composer templates
 531		 * used for editing instances of the BlockType while in the composer ui in the dashboard
 532		 * @return TemplateFile[]
 533		 */
 534		function getBlockTypeComposerTemplates() {
 535			$btHandle = $this->getBlockTypeHandle();
 536			$files = array();
 537			$fh = Loader::helper('file');
 538			$dir = DIR_FILES_BLOCK_TYPES . "/{$btHandle}/" . DIRNAME_BLOCK_TEMPLATES_COMPOSER;
 539			if(is_dir($dir)) {
 540				$files = array_merge($files, $fh->getDirectoryContents($dir));
 541			}
 542			$dir = DIR_FILES_BLOCK_TYPES_CORE . "/{$btHandle}/" . DIRNAME_BLOCK_TEMPLATES_COMPOSER;
 543			if (file_exists($dir)) {
 544				$files = array_merge($files, $fh->getDirectoryContents($dir));
 545			}
 546			Loader::library('template_file');
 547			$templates = array();
 548			foreach(array_unique($files) as $file) {
 549				$templates[] = new TemplateFile($this, $file);
 550			}
 551			return TemplateFile::sortTemplateFileList($templates);
 552		}
 553		
 554		function setBlockTypeDisplayOrder($displayOrder) {
 555			$db = Loader::db();
 556			
 557			$displayOrder = intval($displayOrder); //in case displayOrder came from a string (so ADODB escapes it properly)
 558			
 559			$sql = "UPDATE BlockTypes SET btDisplayOrder = btDisplayOrder - 1 WHERE btDisplayOrder > ?";
 560			$vals = array($this->btDisplayOrder);
 561			$db->Execute($sql, $vals);
 562			
 563			$sql = "UPDATE BlockTypes SET btDisplayOrder = btDisplayOrder + 1 WHERE btDisplayOrder >= ?";
 564			$vals = array($displayOrder);
 565			$db->Execute($sql, $vals);
 566			
 567			$sql = "UPDATE BlockTypes SET btDisplayOrder = ? WHERE btID = ?";
 568			$vals = array($displayOrder, $this->btID);
 569			$db->Execute($sql, $vals);
 570			
 571			// now we remove the block type from cache
 572			$ca = new Cache();
 573			$ca->delete('blockTypeByID', $this->btID);
 574			$ca->delete('blockTypeByHandle', $this->btHandle);
 575			$ca->delete('blockTypeList', false);
 576		}
 577
 578		/**
 579		 * installs a new BlockType from a package, 
 580		 * typicaly called from a package controller's install() method durring package installation 
 581		 * @todo Documentation how is the btID used, if you want to reserve/specify a btID??
 582		 * @param string $btHandle the block Type's handle
 583		 * @param Package $pkg
 584		 * @param int $btID if it's an existing block type
 585		 * @return void|string error message
 586		 */
 587		public function installBlockTypeFromPackage($btHandle, $pkg, $btID = 0) {
 588			$dir1 = DIR_PACKAGES . '/' . $pkg->getPackageHandle() . '/' . DIRNAME_BLOCKS;
 589			$dir2 = DIR_PACKAGES_CORE . '/' . $pkg->getPackageHandle() . '/' . DIRNAME_BLOCKS;
 590			
 591			if (file_exists($dir1)) {
 592				$dir = $dir1;
 593				$dirDbXml = $dir;
 594			} else {
 595				$dir = $dir2;
 596				$dirDbXml = $dir;
 597			}
 598
 599			// now we check to see if it's been overridden in the site root and if so we do it there
 600			if ($btID > 0) { 
 601				// this is only necessary when it's an existing refresh
 602				if (file_exists(DIR_FILES_BLOCK_TYPES . '/' . $btHandle . '/' . FILENAME_BLOCK_CONTROLLER)) {
 603					$dir = DIR_FILES_BLOCK_TYPES;
 604				}
 605				if (file_exists(DIR_FILES_BLOCK_TYPES . '/' . $btHandle . '/' . FILENAME_BLOCK_DB)) {
 606					$dirDbXml = DIR_FILES_BLOCK_TYPES;
 607				}
 608			}
 609			
 610			$bt = new BlockType;
 611			$bt->btHandle = $btHandle;
 612			$bt->pkgHandle = $pkg->getPackageHandle();
 613			$bt->pkgID = $pkg->getPackageID();
 614			return BlockType::doInstallBlockType($btHandle, $bt, $dir, $btID, $dirDbXml);
 615		}
 616		
 617		/**
 618		 * refreshes the BlockType's database schema throws an Exception if error
 619		 * @return void
 620		 */
 621		public function refresh() {
 622			if ($this->getPackageID() > 0) {
 623				$pkg = Package::getByID($this->getPackageID());
 624				$resp = BlockType::installBlockTypeFromPackage($this->getBlockTypeHandle(), $pkg, $this->getBlockTypeID());			
 625				if ($resp != '') {
 626					throw new Exception($resp);
 627				}
 628			} else {
 629				$resp = BlockType::installBlockType($this->getBlockTypeHandle(), $this->getBlockTypeID());			
 630				if ($resp != '') {
 631					throw new Exception($resp);
 632				}
 633			}
 634		}
 635		
 636		/**
 637		 * installs a core or root level BlockType (from /blocks or /concrete/blocks, not a package)
 638		 * should likely be a static method
 639		 * @param string $btHandle
 640		 * @param int $btID btID if it's an existing block type
 641		 */
 642		public function installBlockType($btHandle, $btID = 0) {
 643		
 644			if ($btID == 0) {
 645				// then we don't allow one to already exist
 646				$db = Loader::db();
 647				$cnt = $db->GetOne("select btID from BlockTypes where btHandle = ?", array($btHandle));
 648				if ($cnt > 0) {
 649					return false;
 650				}
 651			}
 652			
 653			if (file_exists(DIR_FILES_BLOCK_TYPES . '/' . $btHandle . '/' . FILENAME_BLOCK_CONTROLLER)) {
 654				$dir = DIR_FILES_BLOCK_TYPES;
 655			} else {
 656				$dir = DIR_FILES_BLOCK_TYPES_CORE;
 657			}
 658			if (file_exists(DIR_FILES_BLOCK_TYPES . '/' . $btHandle . '/' . FILENAME_BLOCK_DB)) {
 659				$dirDbXml = DIR_FILES_BLOCK_TYPES;
 660			} else {
 661				$dirDbXml = DIR_FILES_BLOCK_TYPES_CORE;
 662			}
 663			
 664			$bt = new BlockType;
 665			$bt->btHandle = $btHandle;
 666			$bt->pkgHandle = null;
 667			$bt->pkgID = 0;
 668			return BlockType::doInstallBlockType($btHandle, $bt, $dir, $btID, $dirDbXml);
 669		}
 670		
 671		/** 
 672		 * Renders a particular view of a block type, using the public $controller variable as the block type's controller
 673		 * @param string template 'view' for the default
 674		 * @return void
 675		 */
 676		public function render($view = 'view') {
 677			$bv = new BlockView();
 678			$bv->setController($this->controller);
 679			$bv->render($this, $view);
 680		}			
 681		
 682		/**
 683		 * get's the block type controller
 684		 * @return BlockTypeController
 685		 */
 686		public function getController() {
 687			return $this->controller;
 688		}
 689		
 690		/**
 691		 * installs a block type
 692		 * @param string $btHandle
 693		 * @param BlockType $bt
 694		 * @param string $dir
 695		 * @param int $btID
 696		 * @param string $dirDbXml
 697		 */
 698		protected function doInstallBlockType($btHandle, $bt, $dir, $btID = 0, $dirDbXml) {
 699			$db = Loader::db();
 700			$env = Environment::get();
 701			$env->clearOverrideCache();
 702			
 703			if (file_exists($dir . '/' . $btHandle . '/' . FILENAME_BLOCK_CONTROLLER)) {
 704				$class = $bt->getBlockTypeClass();
 705				
 706				$path = $dirDbXml . '/' . $btHandle;
 707				if (!class_exists($class)) {
 708					require_once($dir . '/' . $btHandle . '/' . FILENAME_BLOCK_CONTROLLER);
 709				}
 710				
 711				if (!class_exists($class)) {
 712					throw new Exception(t("%s not found. Please check that the block controller file contains the correct class name.", $class));
 713				}
 714				$bta = new $class;
 715				
 716				//Attempt to run the subclass methods (install schema from db.xml, etc.)
 717				$r = $bta->install($path);
 718
 719				//Validate
 720				if ($r === false) {
 721					return t('Error: Block Type cannot be installed because no db.xml file can be found. Either create a db.xml file for this block type, or remove the $btTable variable from its controller.');
 722				} else if (!$r->result && $r->message) {
 723					return $r->message;
 724				} else if (!$r->result && !$r->message) {
 725					return t('Error: Block Type cannot be installed due to an unknown database error. Please check that the db.xml file for this block type is formatted correctly.');
 726				} else if ($message = BlockType::validateInstalledDatabaseTable($bta->getBlockTypeDatabaseTable())) {
 727					$db->Execute('DROP TABLE IF EXISTS ' . $bta->getBlockTypeDatabaseTable());
 728					return $message;
 729				}
 730				
 731				$currentLocale = Localization::activeLocale();
 732				if ($currentLocale != 'en_US') {
 733					// Prevent the database records being stored in wrong language
 734					Localization::changeLocale('en_US');
 735				}
 736
 737				//Install the block
 738				$btd = new BlockTypeDB();
 739				$btd->btHandle = $btHandle;
 740				$btd->btName = $bta->getBlockTypeName();
 741				$btd->btDescription = $bta->getBlockTypeDescription();
 742				$btd->btActiveWhenAdded = $bta->isActiveWhenAdded();
 743				$btd->btCopyWhenPropagate = $bta->isCopiedWhenPropagated();
 744				$btd->btIncludeAll = $bta->includeAll();
 745				$btd->btIsInternal = $bta->isBlockTypeInternal();
 746				$btd->btInterfaceHeight = $bta->getInterfaceHeight();
 747				$btd->btInterfaceWidth = $bta->getInterfaceWidth();
 748				$btd->pkgID = $bt->getPackageID();
 749				if ($currentLocale != 'en_US') {
 750					Localization::changeLocale($currentLocale);
 751				}
 752				
 753				if ($btID > 0) {
 754					$btd->btID = $btID;
 755					$btDisplayOrder = $db->GetOne('select btDisplayOrder from BlockTypes where btID = ?', array($btID));
 756					if (!$btDisplayOrder) {
 757						$btDisplayOrder = 0;
 758					}
 759					$btd->btDisplayOrder = $btDisplayOrder;
 760					$r = $btd->Replace();
 761				} else {
 762					if ($bta->isBlockTypeInternal()) {
 763						$btd->btDisplayOrder = 0;
 764					} else {
 765						$btMax = $db->GetOne('select max(btDisplayOrder) from BlockTypes');
 766						if ($btMax < 1 && $btMax !== '0') {
 767							$btd->btDisplayOrder = 0;
 768						} else {
 769							$btd->btDisplayOrder = $btMax + 1;
 770						}
 771					}
 772
 773					$r = $btd->save();
 774				}
 775				
 776				// now we remove the block type from cache
 777				$ca = new Cache();
 778				$ca->delete('blockTypeByID', $btID);
 779				$ca->delete('blockTypeByHandle', $btHandle);
 780				$ca->delete('blockTypeList', false);		 	
 781
 782				if (!$r) {
 783					return $db->ErrorMsg();
 784				}
 785			} else {
 786				return t("No block found with the handle %s.", $btHandle);
 787			}
 788		}
 789		
 790		/**
 791		 * Internal helper function for doInstallBlockType().
 792		 * 
 793		 * Checks if the database table with the given name
 794		 * has at least 2 fields and one of those is called "btID".
 795		 *
 796		 * If valid, we return an empty string.
 797		 * If not valid, we return an error message.
 798		 * If passed an empty string, we return an empty string.
 799		 */
 800		private function validateInstalledDatabaseTable($btTable) {
 801			$message = '';
 802			if (!empty($btTable)) {
 803				$fields = Loader::db()->MetaColumnNames($btTable);
 804				if (count($fields) < 2) {
 805					$message = t('Error: Block Type table must contain at least two fields.');
 806				} else if (!in_array('bID', $fields)) {
 807					$message = t('Error: Block Type table must contain a "bID" primary key field.');
 808				}
 809			}
 810			return $message;
 811		}
 812		
 813		/*
 814		 * Returns a path to where the block type's files are located.
 815		 * @access public
 816		 * @return string $path
 817		 */
 818		public function getBlockTypePath() {
 819			if ($this->getPackageID() > 0) {
 820				$pkgHandle = $this->getPackageHandle();
 821				$dirp = (is_dir(DIR_PACKAGES . '/' . $pkgHandle)) ? DIR_PACKAGES : DIR_PACKAGES_CORE;
 822				$dir = $dirp . '/' . $pkgHandle . '/' . DIRNAME_BLOCKS . '/' . $this->getBlockTypeHandle();
 823			} else {
 824				if (is_dir(DIR_FILES_BLOCK_TYPES . '/' . $this->getBlockTypeHandle())) {
 825					$dir = DIR_FILES_BLOCK_TYPES . '/' . $this->getBlockTypeHandle();
 826				} else {
 827					$dir = DIR_FILES_BLOCK_TYPES_CORE . '/' . $this->getBlockTypeHandle();
 828				}
 829			}
 830			return $dir;	
 831		}
 832		
 833/** @todo Continue documenting from here down **/
 834		
 835		 
 836		/*
 837		 * @access private
 838		 *
 839		 */
 840		protected function _getClass() {
 841
 842			$btHandle = $this->btHandle;
 843			$pkgHandle = $this->getPackageHandle();
 844			$env = Environment::get();
 845			require_once($env->getPath(DIRNAME_BLOCKS . '/' . $this->btHandle . '/' . FILENAME_BLOCK_CONTROLLER, $pkgHandle));
 846			
 847			// takes the handle and performs some magic to get the class;
 848			$btHandle = $this->getBlockTypeHandle();
 849			// split by underscores or dashes
 850			$words = preg_split('/\_|\-/', $btHandle);
 851			for ($i = 0; $i < count($words); $i++) {
 852				$words[$i] = ucfirst($words[$i]);
 853			}
 854			
 855			$class = implode('', $words);
 856			$class = $class . 'BlockController';
 857			return $class;
 858		}
 859		
 860		public function inc($file, $args = array()) {
 861			extract($args);
 862			$bt = $this;
 863			global $c;
 864			global $a;
 865			$env = Environment::get();
 866			include($env->getPath(DIRNAME_BLOCKS . '/' . $this->getBlockTypeHandle() . '/' . $file, $this->getPackageHandle()));
 867		}
 868		
 869		public function getBlockTypeClass() {
 870			return $this->_getClass();
 871		}
 872		
 873		/**
 874		 * Deprecated -- use getBlockTypeClass() instead.
 875		 */
 876		public function getBlockTypeClassFromHandle() {
 877			return $this->getBlockTypeClass();
 878		}
 879		
 880		/** 
 881		 * Removes the block type. Also removes instances of content.
 882		 */
 883		public function delete() {
 884			$db = Loader::db();
 885			$r = $db->Execute('select cID, cvID, b.bID, arHandle from CollectionVersionBlocks cvb inner join Blocks b on b.bID = cvb.bID where btID = ?', array($this->getBlockTypeID()));
 886			while ($row = $r->FetchRow()) {
 887				$nc = Page::getByID($row['cID'], $row['cvID']);
 888				if(!is_object($nc) || $nc->isError()) continue;
 889				$b = Block::getByID($row['bID'], $nc, $row['arHandle']);
 890				if (is_object($b)) {
 891					$b->deleteBlock();
 892				}
 893			}
 894			
 895			$ca = new Cache();
 896			$ca->delete('blockTypeByID', $this->btID);
 897			$ca->delete('blockTypeByHandle', $this->btHandle);		 	
 898			$ca->delete('blockTypeList', false);		 	
 899			$db->Execute("delete from BlockTypes where btID = ?", array($this->btID));
 900			
 901			//Remove gaps in display order numbering (to avoid future sorting errors)
 902			BlockTypeList::resetBlockTypeDisplayOrder('btDisplayOrder');
 903		}
 904		
 905		/** 
 906		 * Allows block types to be updated
 907		 * @param array $data
 908		 */
 909		 
 910		 public function update($data) {
 911		 	$db = Loader::db();
 912		 	$btHandle = $this->btHandle;
 913		 	$btName = $this->btName;
 914		 	$btDescription = $this->btDescription;
 915		 	if (isset($data['btHandle'])) {
 916		 		$btHandle = $data['btHandle'];
 917		 	}
 918		 	if (isset($data['btName'])) {
 919		 		$btName = $data['btName'];
 920		 	}
 921		 	if (isset($data['btDescription'])) {
 922		 		$btDescription = $data['btDescription'];
 923		 	}
 924		 	$db->Execute('update BlockTypes set btHandle = ?, btName = ?, btDescription = ? where btID = ?', array($btHandle, $btName, $btDescription, $this->btID));
 925
 926			// now we remove the block type from cache
 927			$ca = new Cache();
 928			$ca->delete('blockTypeByID', $this->btID);
 929			$ca->delete('blockTypeByHandle', $btHandle);
 930			$ca->delete('blockTypeList', false);		 	
 931		 }
 932		 
 933		 
 934		/* 
 935		 * Adds a block to the system without adding it to a collection. 
 936		 * Passes page and area data along if it is available, however.
 937		 */
 938		public function add($data, $c = false, $a = false) {
 939			$db = Loader::db();
 940			
 941			$u = new User();
 942			if (isset($data['uID'])) {
 943				$uID = $data['uID'];
 944			} else { 
 945				$uID = $u->getUserID();
 946			}
 947			$btID = $this->btID;
 948			$dh = Loader::helper('date');
 949			$bDate = $dh->getSystemDateTime();
 950			$bIsActive = ($this->btActiveWhenAdded == 1) ? 1 : 0;
 951			
 952			$v = array($_POST['bName'], $bDate, $bDate, $bIsActive, $btID, $uID);
 953			$q = "insert into Blocks (bName, bDateAdded, bDateModified, bIsActive, btID, uID) values (?, ?, ?, ?, ?, ?)";
 954			
 955			$r = $db->prepare($q);
 956			$res = $db->execute($r, $v);
 957
 958			$bIDnew = $db->Insert_ID();
 959
 960			// we get the block object for the block we just added
 961
 962			if ($res) {
 963				$nb = Block::getByID($bIDnew);
 964	
 965				$btHandle = $this->getBlockTypeHandle();
 966				
 967				$class = $this->getBlockTypeClass();
 968				$bc = new $class($nb);
 969				if (is_object($c)) {
 970					$bc->setCollectionObject($c);
 971				}
 972				$bc->save($data);
 973				
 974				// the previous version of the block above is cached without the values				
 975				$nb->refreshCache();
 976				
 977				return Block::getByID($bIDnew);
 978				
 979			}
 980			
 981		}
 982		
 983		function getBlockTypeID() {
 984			return $this->btID;
 985		}
 986		
 987		function getBlockTypeHandle() {
 988			return $this->btHandle;
 989		}
 990		
 991		// getBlockAddAction vs. getBlockTypeAddAction() - The difference is very simple. We call getBlockTypeAddAction() to grab the
 992		// action properties for the form that presents the drop-down select menu for selecting which type of block to add. We call the other
 993		// function when we've already chosen a type to add, and we're interested in actually adding the block - content completed - to the database
 994				
 995		function getBlockAddAction(&$a, $alternateHandler = null) {
 996			// Note: This is fugly, since we're just grabbing query string variables, but oh well. Not _everything_ can be object oriented
 997			$btID = $this->btID;
 998			$step = ($_REQUEST['step']) ? '&step=' . $_REQUEST['step'] : '';			
 999			$c = $a->getAreaCollectionObject();
1000			$cID = $c->getCollectionID();
1001			$arHandle = urlencode($a->getAreaHandle());
1002			$valt = Loader::helper('validation/token');
1003			
1004			
1005			if ($alternateHandler) {
1006				$str = $alternateHandler . "?cID={$cID}&arHandle={$arHandle}&btID={$btID}&mode=edit" . $step . '&' . $valt->getParameter();
1007			} else {
1008				$str = DIR_REL . "/" . DISPATCHER_FILENAME . "?cID={$cID}&arHandle={$arHandle}&btID={$btID}&mode=edit" . $step . '&' . $valt->getParameter();
1009			}
1010			return $str;			
1011		}
1012		
1013		
1014		function getBlockTypeName() {
1015			return $this->btName;
1016		}
1017		
1018		function isInstalled() {
1019			return $this->installed;
1020		}
1021		
1022		function getBlockTypeActiveWhenAdded() {
1023			return $this->btActiveWhenAdded;
1024		}
1025		
1026		function isCopiedWhenPropagated() {
1027			return $this->btCopyWhenPropagate;
1028		}
1029
1030		function includeAll() {
1031			return $this->btIncludeAll;
1032		}
1033		
1034		function hasCustomEditTemplate() {
1035			return $this->hasCustomEditTemplate;
1036		}
1037		
1038		function hasCustomViewTemplate() {
1039			return $this->hasCustomViewTemplate;
1040		}
1041		
1042		function hasCustomAddTemplate() {
1043			return $this->hasCustomAddTemplate;
1044		}
1045
1046	}